<commit_before>package utp

/*
#include "utp.h"
*/
import "C"
import (
	"context"
	"errors"
	"net"
	"sync/atomic"
	"time"

	"github.com/anacrolix/missinggo/inproc"
)

type Socket struct {
	pc            net.PacketConn
	ctx           *C.utp_context
	backlog       chan *Conn
	closed        bool
	conns         map[*C.utp_socket]*Conn
	nonUtpReads   chan packet
	writeDeadline time.Time
	readDeadline  time.Time
}

var (
	_ net.PacketConn = (*Socket)(nil)
)

type packet struct {
	b    []byte
	from net.Addr
}

func listenPacket(network, addr string) (pc net.PacketConn, err error) {
	if network == "inproc" {
		return inproc.ListenPacket(network, addr)
	}
	return net.ListenPacket(network, addr)
}

func NewSocket(network, addr string) (*Socket, error) {
	pc, err := listenPacket(network, addr)
	if err != nil {
		return nil, err
	}
	mu.Lock()
	defer mu.Unlock()
	ctx := C.utp_init(2)
	if ctx == nil {
		panic(ctx)
	}
	ctx.setCallbacks()
	if utpLogging {
		ctx.setOption(C.UTP_LOG_NORMAL, 1)
		ctx.setOption(C.UTP_LOG_MTU, 1)
		ctx.setOption(C.UTP_LOG_DEBUG, 1)
	}
	s := &Socket{
		pc:          pc,
		ctx:         ctx,
		backlog:     make(chan *Conn, 5),
		conns:       make(map[*C.utp_socket]*Conn),
		nonUtpReads: make(chan packet, 100),
	}
	libContextToSocket[ctx] = s
	go s.timeoutChecker()
	go s.packetReader()
	return s, nil
}

func (s *Socket) onLibSocketDestroyed(ls *C.utp_socket) {
	delete(s.conns, ls)
}

func (s *Socket) newConn(us *C.utp_socket) *Conn {
	c := &Conn{
		s: us,
	}
	c.cond.L = &mu
	s.conns[us] = c
	c.writeDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	c.readDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	return c
}

var reads int64

func (s *Socket) packetReader() {
	var b [0x1000]byte
	for {
		// In C, all the reads are processed and when it threatens to block,
		// only then do we call utp_issue_deferred_acks. I don't know how we
		// can do this in Go.
		n, addr, err := s.pc.ReadFrom(b[:])
		if err != nil {
			mu.Lock()
			closed := s.closed
			mu.Unlock()
			if closed {
				return
			}
			panic(err)
		}
		sa, sal := netAddrToLibSockaddr(addr)
		atomic.AddInt64(&reads, 1)
		// log.Printf("received %d bytes, %d packets", n, reads)
		func() {
			mu.Lock()
			defer mu.Unlock()
			if s.closed {
				return
			}
			ret := C.utp_process_udp(s.ctx, (*C.byte)(&b[0]), C.size_t(n), sa, sal)
			switch ret {
			case 1:
				socketUtpPacketsReceived.Add(1)
				C.utp_issue_deferred_acks(s.ctx)
				C.utp_check_timeouts(s.ctx)
			case 0:
				s.onReadNonUtp(b[:n], addr)
			default:
				panic(ret)
			}
		}()
	}
}

func (s *Socket) timeoutChecker() {
	for {
		mu.Lock()
		if s.closed {
			mu.Unlock()
			return
		}
		// C.utp_issue_deferred_acks(s.ctx)
		C.utp_check_timeouts(s.ctx)
		mu.Unlock()
		time.Sleep(500 * time.Millisecond)
	}
}

func (me *Socket) Close() error {
	mu.Lock()
	defer mu.Unlock()
	if me.closed {
		return nil
	}
	// Calling this deletes the pointer. It must not be referred to after
	// this.
	C.utp_destroy(me.ctx)
	me.ctx = nil
	me.pc.Close()
	close(me.backlog)
	close(me.nonUtpReads)
	me.closed = true
	return nil
}

func (me *Socket) Addr() net.Addr {
	return me.pc.LocalAddr()
}

func (me *Socket) LocalAddr() net.Addr {
	return me.pc.LocalAddr()
}

func (s *Socket) Accept() (net.Conn, error) {
	nc, ok := <-s.backlog
	if !ok {
		return nil, errors.New("closed")
	}
	return nc, nil
}

func (s *Socket) Dial(addr string) (net.Conn, error) {
	return s.DialTimeout(addr, 0)
}

func (s *Socket) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
	ctx := context.Background()
	if timeout != 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}
	return s.DialContext(ctx, addr)
}

func (s *Socket) resolveAddr(addr string) (net.Addr, error) {
	n := s.Addr().Network()
	switch n {
	case "inproc":
		return inproc.ResolveAddr(n, addr)
	default:
		return net.ResolveUDPAddr(n, addr)
	}
}

func (s *Socket) DialContext(ctx context.Context, addr string) (net.Conn, error) {
	ua, err := s.resolveAddr(addr)
	if err != nil {
		panic(err)
	}
	sa, sl := netAddrToLibSockaddr(ua)
	mu.Lock()
	defer mu.Unlock()
	if s.closed {
		return nil, errors.New("socket closed")
	}
	c := s.newConn(C.utp_create_socket(s.ctx))
	C.utp_connect(c.s, sa, sl)
	err = c.waitForConnect(ctx)
	if err != nil {
		c.close()
		return nil, err
	}
	return c, nil
}

func (s *Socket) pushBacklog(c *Conn) {
	select {
	case s.backlog <- c:
	default:
		c.close()
	}
}

func (s *Socket) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	p, ok := <-s.nonUtpReads
	if !ok {
		err = errors.New("closed")
		return
	}
	n = copy(b, p.b)
	addr = p.from
	return
}

func (s *Socket) onReadNonUtp(b []byte, from net.Addr) {
	socketNonUtpPacketsReceived.Add(1)
	select {
	case s.nonUtpReads <- packet{append([]byte(nil), b...), from}:
	default:
		// log.Printf("dropped non utp packet: no room in buffer")
		nonUtpPacketsDropped.Add(1)
	}
}

func (s *Socket) SetReadDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) SetWriteDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) SetDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error) {
	return s.pc.WriteTo(b, addr)
}

func (s *Socket) ReadBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_RCVBUF))
}

func (s *Socket) WriteBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_SNDBUF))
}

func (s *Socket) SetWriteBufferLen(len int) {
	mu.Lock()
	defer mu.Unlock()
	i := C.utp_context_set_option(s.ctx, C.UTP_SNDBUF, C.int(len))
	if i != 0 {
		panic(i)
	}
}
<commit_msg>Add workaround for https://github.com/anacrolix/torrent/issues/83<commit_after>package utp

/*
#include "utp.h"
*/
import "C"
import (
	"context"
	"errors"
	"log"
	"net"
	"sync/atomic"
	"time"

	"github.com/anacrolix/missinggo/inproc"
)

type Socket struct {
	pc            net.PacketConn
	ctx           *C.utp_context
	backlog       chan *Conn
	closed        bool
	conns         map[*C.utp_socket]*Conn
	nonUtpReads   chan packet
	writeDeadline time.Time
	readDeadline  time.Time
}

var (
	_ net.PacketConn = (*Socket)(nil)
)

type packet struct {
	b    []byte
	from net.Addr
}

func listenPacket(network, addr string) (pc net.PacketConn, err error) {
	if network == "inproc" {
		return inproc.ListenPacket(network, addr)
	}
	return net.ListenPacket(network, addr)
}

func NewSocket(network, addr string) (*Socket, error) {
	pc, err := listenPacket(network, addr)
	if err != nil {
		return nil, err
	}
	mu.Lock()
	defer mu.Unlock()
	ctx := C.utp_init(2)
	if ctx == nil {
		panic(ctx)
	}
	ctx.setCallbacks()
	if utpLogging {
		ctx.setOption(C.UTP_LOG_NORMAL, 1)
		ctx.setOption(C.UTP_LOG_MTU, 1)
		ctx.setOption(C.UTP_LOG_DEBUG, 1)
	}
	s := &Socket{
		pc:          pc,
		ctx:         ctx,
		backlog:     make(chan *Conn, 5),
		conns:       make(map[*C.utp_socket]*Conn),
		nonUtpReads: make(chan packet, 100),
	}
	libContextToSocket[ctx] = s
	go s.timeoutChecker()
	go s.packetReader()
	return s, nil
}

func (s *Socket) onLibSocketDestroyed(ls *C.utp_socket) {
	delete(s.conns, ls)
}

func (s *Socket) newConn(us *C.utp_socket) *Conn {
	c := &Conn{
		s: us,
	}
	c.cond.L = &mu
	s.conns[us] = c
	c.writeDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	c.readDeadlineTimer = time.AfterFunc(-1, c.cond.Broadcast)
	return c
}

var reads int64

func (s *Socket) packetReader() {
	var b [0x1000]byte
	for {
		// In C, all the reads are processed and when it threatens to block,
		// only then do we call utp_issue_deferred_acks. I don't know how we
		// can do this in Go.
		n, addr, err := s.pc.ReadFrom(b[:])
		if err != nil {
			mu.Lock()
			closed := s.closed
			mu.Unlock()
			if closed {
				return
			}
			// See https://github.com/anacrolix/torrent/issues/83. If we get
			// an endless stream of errors (such as the PacketConn being
			// Closed outside of our control, this work around may need to be
			// reconsidered.
			log.Print(err)
			continue
		}
		sa, sal := netAddrToLibSockaddr(addr)
		atomic.AddInt64(&reads, 1)
		// log.Printf("received %d bytes, %d packets", n, reads)
		func() {
			mu.Lock()
			defer mu.Unlock()
			if s.closed {
				return
			}
			ret := C.utp_process_udp(s.ctx, (*C.byte)(&b[0]), C.size_t(n), sa, sal)
			switch ret {
			case 1:
				socketUtpPacketsReceived.Add(1)
				C.utp_issue_deferred_acks(s.ctx)
				C.utp_check_timeouts(s.ctx)
			case 0:
				s.onReadNonUtp(b[:n], addr)
			default:
				panic(ret)
			}
		}()
	}
}

func (s *Socket) timeoutChecker() {
	for {
		mu.Lock()
		if s.closed {
			mu.Unlock()
			return
		}
		// C.utp_issue_deferred_acks(s.ctx)
		C.utp_check_timeouts(s.ctx)
		mu.Unlock()
		time.Sleep(500 * time.Millisecond)
	}
}

func (me *Socket) Close() error {
	mu.Lock()
	defer mu.Unlock()
	if me.closed {
		return nil
	}
	// Calling this deletes the pointer. It must not be referred to after
	// this.
	C.utp_destroy(me.ctx)
	me.ctx = nil
	me.pc.Close()
	close(me.backlog)
	close(me.nonUtpReads)
	me.closed = true
	return nil
}

func (me *Socket) Addr() net.Addr {
	return me.pc.LocalAddr()
}

func (me *Socket) LocalAddr() net.Addr {
	return me.pc.LocalAddr()
}

func (s *Socket) Accept() (net.Conn, error) {
	nc, ok := <-s.backlog
	if !ok {
		return nil, errors.New("closed")
	}
	return nc, nil
}

func (s *Socket) Dial(addr string) (net.Conn, error) {
	return s.DialTimeout(addr, 0)
}

func (s *Socket) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
	ctx := context.Background()
	if timeout != 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}
	return s.DialContext(ctx, addr)
}

func (s *Socket) resolveAddr(addr string) (net.Addr, error) {
	n := s.Addr().Network()
	switch n {
	case "inproc":
		return inproc.ResolveAddr(n, addr)
	default:
		return net.ResolveUDPAddr(n, addr)
	}
}

func (s *Socket) DialContext(ctx context.Context, addr string) (net.Conn, error) {
	ua, err := s.resolveAddr(addr)
	if err != nil {
		panic(err)
	}
	sa, sl := netAddrToLibSockaddr(ua)
	mu.Lock()
	defer mu.Unlock()
	if s.closed {
		return nil, errors.New("socket closed")
	}
	c := s.newConn(C.utp_create_socket(s.ctx))
	C.utp_connect(c.s, sa, sl)
	err = c.waitForConnect(ctx)
	if err != nil {
		c.close()
		return nil, err
	}
	return c, nil
}

func (s *Socket) pushBacklog(c *Conn) {
	select {
	case s.backlog <- c:
	default:
		c.close()
	}
}

func (s *Socket) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
	p, ok := <-s.nonUtpReads
	if !ok {
		err = errors.New("closed")
		return
	}
	n = copy(b, p.b)
	addr = p.from
	return
}

func (s *Socket) onReadNonUtp(b []byte, from net.Addr) {
	socketNonUtpPacketsReceived.Add(1)
	select {
	case s.nonUtpReads <- packet{append([]byte(nil), b...), from}:
	default:
		// log.Printf("dropped non utp packet: no room in buffer")
		nonUtpPacketsDropped.Add(1)
	}
}

func (s *Socket) SetReadDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) SetWriteDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) SetDeadline(t time.Time) error {
	panic("not implemented")
}

func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error) {
	return s.pc.WriteTo(b, addr)
}

func (s *Socket) ReadBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_RCVBUF))
}

func (s *Socket) WriteBufferLen() int {
	mu.Lock()
	defer mu.Unlock()
	return int(C.utp_context_get_option(s.ctx, C.UTP_SNDBUF))
}

func (s *Socket) SetWriteBufferLen(len int) {
	mu.Lock()
	defer mu.Unlock()
	i := C.utp_context_set_option(s.ctx, C.UTP_SNDBUF, C.int(len))
	if i != 0 {
		panic(i)
	}
}
<|endoftext|>


<commit_before>package peco

import (
	"bufio"
	"io"
	"sync"
	"time"

	"github.com/lestrrat/go-pdebug"
	"github.com/peco/peco/internal/util"
	"github.com/peco/peco/pipeline"
	"golang.org/x/net/context"
)

// Creates a new Source. Does not start processing the input until you
// call Setup()
func NewSource(in io.Reader, idgen lineIDGenerator, capacity int, enableSep bool) *Source {
	s := &Source{
		in:            in, // Note that this may be closed, so do not rely on it
		capacity:      capacity,
		enableSep:     enableSep,
		idgen:         idgen,
		ready:         make(chan struct{}),
		setupDone:     make(chan struct{}),
		setupOnce:     sync.Once{},
		OutputChannel: pipeline.OutputChannel(make(chan interface{})),
	}
	s.Reset()
	return s
}

// Setup reads from the input os.File.
func (s *Source) Setup(ctx context.Context, state *Peco) {
	s.setupOnce.Do(func() {
		done := make(chan struct{})
		refresh := make(chan struct{}, 1)
		defer close(done)
		defer close(refresh)
		// And also, close the done channel so we can tell the consumers
		// we have finished reading everything
		defer close(s.setupDone)

		draw := func(state *Peco) {
			state.Hub().SendDraw(nil)
		}

		go func() {
			ticker := time.NewTicker(100 * time.Millisecond)
			defer ticker.Stop()

			for {
				select {
				case <-done:
					draw(state)
					return
				case <-ticker.C:
					draw(state)
				}
			}
		}()

		// This sync.Once var is used to receive the notification
		// that there was at least 1 line read from the source
		// This is wrapped in a sync.Notify so we can safely call
		// it in multiple places
		var notify sync.Once
		notifycb := func() {
			// close the ready channel so others can be notified
			// that there's at least 1 line in the buffer
			state.Hub().SendStatusMsg("")
			close(s.ready)
		}

		// Register this to be called in a defer, just in case we could bailed
		// out without reading a single line.
		// Note: this will be a no-op if notify.Do has been called before
		defer notify.Do(notifycb)

		scanner := bufio.NewScanner(s.in)
		defer func() {
			if util.IsTty(s.in) {
				return
			}
			if closer, ok := s.in.(io.Closer); ok {
				closer.Close()
			}
		}()

		lines := make(chan string)
		go func() {
			defer close(lines)
			for scanner.Scan() {
				lines <- scanner.Text()
			}
		}()

		state.Hub().SendStatusMsg("Waiting for input...")

		readCount := 0
		for {
			select {
			case <-ctx.Done():
				if pdebug.Enabled {
					pdebug.Printf("Bailing out of source setup, because ctx was canceled")
				}
				return
			case l, ok := <-lines:
				if !ok {
					if pdebug.Enabled {
						pdebug.Printf("No more lines to read...")
					}
					break
				}

				readCount++
				s.Append(NewRawLine(s.idgen.next(), l, s.enableSep))
				notify.Do(notifycb)
			}
		}

		if pdebug.Enabled {
			pdebug.Printf("Read all %d lines from source", readCount)
		}
	})
}

// Start starts
func (s *Source) Start(ctx context.Context, out pipeline.OutputChannel) {
	// I should be the only one running this method until I bail out
	if pdebug.Enabled {
		g := pdebug.Marker("Source.Start")
		defer g.End()
		defer pdebug.Printf("Source sent %d lines", len(s.lines))
	}

	for _, l := range s.lines {
		select {
		case <-ctx.Done():
			if pdebug.Enabled {
				pdebug.Printf("Source: context.Done detected")
			}
			return
		default:
			out.Send(l)
		}
	}
	out.SendEndMark("end of input")
}

// Reset resets the state of the source object so that it
// is ready to feed the filters
func (s *Source) Reset() {
	if pdebug.Enabled {
		g := pdebug.Marker("Source.Reset")
		defer g.End()
	}
	s.OutputChannel = pipeline.OutputChannel(make(chan interface{}))
}

// Ready returns the "input ready" channel. It will be closed as soon as
// the first line of input is processed via Setup()
func (s *Source) Ready() <-chan struct{} {
	return s.ready
}

// SetupDone returns the "read all lines" channel. It will be closed as soon as
// the all input has been read
func (s *Source) SetupDone() <-chan struct{} {
	return s.setupDone
}

func (s *Source) LineAt(n int) (Line, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return bufferLineAt(s.lines, n)
}

func (s *Source) Size() int {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return bufferSize(s.lines)
}

func (s *Source) Append(l Line) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	bufferAppend(&s.lines, l)
	if s.capacity > 0 && len(s.lines) > s.capacity {
		diff := len(s.lines) - s.capacity

		// Golang's version of array realloc
		s.lines = s.lines[diff:s.capacity:s.capacity]
	}
}
<commit_msg>seems like we failed to bail out of the for<commit_after>package peco

import (
	"bufio"
	"io"
	"sync"
	"time"

	"github.com/lestrrat/go-pdebug"
	"github.com/peco/peco/internal/util"
	"github.com/peco/peco/pipeline"
	"golang.org/x/net/context"
)

// Creates a new Source. Does not start processing the input until you
// call Setup()
func NewSource(in io.Reader, idgen lineIDGenerator, capacity int, enableSep bool) *Source {
	s := &Source{
		in:            in, // Note that this may be closed, so do not rely on it
		capacity:      capacity,
		enableSep:     enableSep,
		idgen:         idgen,
		ready:         make(chan struct{}),
		setupDone:     make(chan struct{}),
		setupOnce:     sync.Once{},
		OutputChannel: pipeline.OutputChannel(make(chan interface{})),
	}
	s.Reset()
	return s
}

// Setup reads from the input os.File.
func (s *Source) Setup(ctx context.Context, state *Peco) {
	s.setupOnce.Do(func() {
		done := make(chan struct{})
		refresh := make(chan struct{}, 1)
		defer close(done)
		defer close(refresh)
		// And also, close the done channel so we can tell the consumers
		// we have finished reading everything
		defer close(s.setupDone)

		draw := func(state *Peco) {
			state.Hub().SendDraw(nil)
		}

		go func() {
			ticker := time.NewTicker(100 * time.Millisecond)
			defer ticker.Stop()

			for {
				select {
				case <-done:
					draw(state)
					return
				case <-ticker.C:
					draw(state)
				}
			}
		}()

		// This sync.Once var is used to receive the notification
		// that there was at least 1 line read from the source
		// This is wrapped in a sync.Notify so we can safely call
		// it in multiple places
		var notify sync.Once
		notifycb := func() {
			// close the ready channel so others can be notified
			// that there's at least 1 line in the buffer
			state.Hub().SendStatusMsg("")
			close(s.ready)
		}

		// Register this to be called in a defer, just in case we could bailed
		// out without reading a single line.
		// Note: this will be a no-op if notify.Do has been called before
		defer notify.Do(notifycb)

		scanner := bufio.NewScanner(s.in)
		defer func() {
			if util.IsTty(s.in) {
				return
			}
			if closer, ok := s.in.(io.Closer); ok {
				closer.Close()
			}
		}()

		lines := make(chan string)
		go func() {
			defer close(lines)
			for scanner.Scan() {
				lines <- scanner.Text()
			}
		}()

		state.Hub().SendStatusMsg("Waiting for input...")

		readCount := 0
		for loop := true; loop; {
			select {
			case <-ctx.Done():
				if pdebug.Enabled {
					pdebug.Printf("Bailing out of source setup, because ctx was canceled")
				}
				return
			case l, ok := <-lines:
				if !ok {
					if pdebug.Enabled {
						pdebug.Printf("No more lines to read...")
					}
					loop = false
					break
				}

				readCount++
				s.Append(NewRawLine(s.idgen.next(), l, s.enableSep))
				notify.Do(notifycb)
			}
		}

		if pdebug.Enabled {
			pdebug.Printf("Read all %d lines from source", readCount)
		}
	})
}

// Start starts
func (s *Source) Start(ctx context.Context, out pipeline.OutputChannel) {
	// I should be the only one running this method until I bail out
	if pdebug.Enabled {
		g := pdebug.Marker("Source.Start")
		defer g.End()
		defer pdebug.Printf("Source sent %d lines", len(s.lines))
	}

	for _, l := range s.lines {
		select {
		case <-ctx.Done():
			if pdebug.Enabled {
				pdebug.Printf("Source: context.Done detected")
			}
			return
		default:
			out.Send(l)
		}
	}
	out.SendEndMark("end of input")
}

// Reset resets the state of the source object so that it
// is ready to feed the filters
func (s *Source) Reset() {
	if pdebug.Enabled {
		g := pdebug.Marker("Source.Reset")
		defer g.End()
	}
	s.OutputChannel = pipeline.OutputChannel(make(chan interface{}))
}

// Ready returns the "input ready" channel. It will be closed as soon as
// the first line of input is processed via Setup()
func (s *Source) Ready() <-chan struct{} {
	return s.ready
}

// SetupDone returns the "read all lines" channel. It will be closed as soon as
// the all input has been read
func (s *Source) SetupDone() <-chan struct{} {
	return s.setupDone
}

func (s *Source) LineAt(n int) (Line, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return bufferLineAt(s.lines, n)
}

func (s *Source) Size() int {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return bufferSize(s.lines)
}

func (s *Source) Append(l Line) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	bufferAppend(&s.lines, l)
	if s.capacity > 0 && len(s.lines) > s.capacity {
		diff := len(s.lines) - s.capacity

		// Golang's version of array realloc
		s.lines = s.lines[diff:s.capacity:s.capacity]
	}
}
<|endoftext|>


<commit_before>package platform

import (
	"context"
	"fmt"
)

type Error string

func (e Error) Error() string {
	return string(e)
}

const (
	ErrSourceNotFound = Error("source not found")
)

// SourceType is a string for types of sources.
type SourceType string

const (
	V2SourceType   = "v2"
	V1SourceType   = "v1"
	SelfSourceType = "self"
)

// Source is an external Influx with time series data.
// TODO(desa): do we still need default?
// TODO(desa): do sources belong
type Source struct {
	ID                 ID         `json:"id,string"`                    // ID is the unique ID of the source
	OrganizationID     ID         `json:"organizationID"`               // OrganizationID is the organization ID that resource belongs to
	Default            bool       `json:"default"`                      // Default specifies the default source for the application
	Name               string     `json:"name"`                         // Name is the user-defined name for the source
	Type               SourceType `json:"type,omitempty"`               // Type specifies which kinds of source (enterprise vs oss vs 2.0)
	URL                string     `json:"url"`                          // URL are the connections to the source
	InsecureSkipVerify bool       `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the source is accepted
	Telegraf           string     `json:"telegraf"`                     // Telegraf is the db telegraf is written to.  By default it is "telegraf"
	SourceFields
	V1SourceFields

	BucketService BucketService `json:"-"`
	// TODO(desa): is this a good idea?
	SourceQuerier SourceQuerier `json:"-"`
}

// V1SourceFields are the fields for connecting to a 1.0 source (oss or enterprise)
type V1SourceFields struct {
	Username     string `json:"username,omitempty"`     // Username is the username to connect to the source
	Password     string `json:"password,omitempty"`     // Password is in CLEARTEXT
	SharedSecret string `json:"sharedSecret,omitempty"` // ShareSecret is the optional signing secret for Influx JWT authorization
	MetaURL      string `json:"metaUrl,omitempty"`      // MetaURL is the url for the meta node
	DefaultRP    string `json:"defaultRP"`              // DefaultRP is the default retention policy used in database queries to this source
	FluxURL      string `json:"fluxURL,omitempty"`      // FluxURL is the url for a flux connected to a 1x source
}

// SourceFields
type SourceFields struct {
	Token string `json:"token"` // Token is the 2.0 authorization token associated with a source
}

// SourceService is a service for managing sources.
type SourceService interface {
	// DefaultSource retrieves the default source.
	DefaultSource(ctx context.Context) (*Source, error)
	// FindSourceByID retrieves a source by its ID.
	FindSourceByID(ctx context.Context, id ID) (*Source, error)
	// FindSources returns a list of all sources.
	FindSources(ctx context.Context, opts FindOptions) ([]*Source, int, error)
	// CreateSource sets the sources ID and stores it.
	CreateSource(ctx context.Context, s *Source) error
	// UpdateSource updates the source.
	UpdateSource(ctx context.Context, id ID, upd SourceUpdate) (*Source, error)
	// DeleteSource removes the source.
	DeleteSource(ctx context.Context, id ID) error
}

// SourceUpdate represents updates to a source.
type SourceUpdate struct {
	Name               *string     `json:"name"`
	Type               *SourceType `json:"type,omitempty"`
	Token              *string     `json:"token"`
	URL                *string     `json:"url"`
	InsecureSkipVerify *bool       `json:"insecureSkipVerify,omitempty"`
	Telegraf           *string     `json:"telegraf"`
	Username           *string     `json:"username,omitempty"`
	Password           *string     `json:"password,omitempty"`
	SharedSecret       *string     `json:"sharedSecret,omitempty"`
	MetaURL            *string     `json:"metaURL,omitempty"`
	FluxURL            *string     `json:"fluxURL,omitempty"`
	Role               *string     `json:"role,omitempty"`
	DefaultRP          *string     `json:"defaultRP"`
}

// Apply applies an update to a source.
func (u SourceUpdate) Apply(s *Source) error {
	if u.Name != nil {
		s.Name = *u.Name
	}
	if u.Type != nil {
		s.Type = *u.Type
	}
	if u.Token != nil {
		s.Token = *u.Token
	}
	if u.URL != nil {
		s.URL = *u.URL
	}
	if u.InsecureSkipVerify != nil {
		s.InsecureSkipVerify = *u.InsecureSkipVerify
	}
	if u.Telegraf != nil {
		s.Telegraf = *u.Telegraf
	}
	if u.Username != nil {
		s.Username = *u.Username
	}
	if u.Password != nil {
		s.Password = *u.Password
	}
	if u.SharedSecret != nil {
		s.SharedSecret = *u.SharedSecret
	}
	if u.MetaURL != nil {
		s.MetaURL = *u.MetaURL
	}
	if u.FluxURL != nil {
		s.FluxURL = *u.FluxURL
	}
	if u.DefaultRP != nil {
		s.DefaultRP = *u.DefaultRP
	}

	return nil
}
func (s *Source) FindBucketByID(ctx context.Context, id ID) (*Bucket, error) {
	if s.BucketService == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.BucketService.FindBucketByID(ctx, id)
}

func (s *Source) FindBucket(ctx context.Context, filter BucketFilter) (*Bucket, error) {
	if s.BucketService == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.BucketService.FindBucket(ctx, filter)
}

func (s *Source) FindBuckets(ctx context.Context, filter BucketFilter, opt ...FindOptions) ([]*Bucket, int, error) {
	if s.BucketService == nil {
		return nil, 0, fmt.Errorf("not supported")
	}
	return s.BucketService.FindBuckets(ctx, filter, opt...)
}

func (s *Source) CreateBucket(ctx context.Context, b *Bucket) error {
	if s.BucketService == nil {
		return fmt.Errorf("not supported")
	}
	return s.BucketService.CreateBucket(ctx, b)
}

func (s *Source) UpdateBucket(ctx context.Context, id ID, upd BucketUpdate) (*Bucket, error) {
	if s.BucketService == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.BucketService.UpdateBucket(ctx, id, upd)
}

func (s *Source) DeleteBucket(ctx context.Context, id ID) error {
	if s.BucketService == nil {
		return fmt.Errorf("not supported")
	}
	return s.BucketService.DeleteBucket(ctx, id)
}
<commit_msg>feat(platform): ensure that Source implements query interface<commit_after>package platform

import (
	"context"
	"fmt"
)

type Error string

func (e Error) Error() string {
	return string(e)
}

const (
	ErrSourceNotFound = Error("source not found")
)

// SourceType is a string for types of sources.
type SourceType string

const (
	V2SourceType   = "v2"
	V1SourceType   = "v1"
	SelfSourceType = "self"
)

// Source is an external Influx with time series data.
// TODO(desa): do we still need default?
// TODO(desa): do sources belong
type Source struct {
	ID                 ID         `json:"id,string"`                    // ID is the unique ID of the source
	OrganizationID     ID         `json:"organizationID"`               // OrganizationID is the organization ID that resource belongs to
	Default            bool       `json:"default"`                      // Default specifies the default source for the application
	Name               string     `json:"name"`                         // Name is the user-defined name for the source
	Type               SourceType `json:"type,omitempty"`               // Type specifies which kinds of source (enterprise vs oss vs 2.0)
	URL                string     `json:"url"`                          // URL are the connections to the source
	InsecureSkipVerify bool       `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the source is accepted
	Telegraf           string     `json:"telegraf"`                     // Telegraf is the db telegraf is written to.  By default it is "telegraf"
	SourceFields
	V1SourceFields

	BucketService BucketService `json:"-"`
	// TODO(desa): is this a good idea?
	SourceQuerier SourceQuerier `json:"-"`
}

// V1SourceFields are the fields for connecting to a 1.0 source (oss or enterprise)
type V1SourceFields struct {
	Username     string `json:"username,omitempty"`     // Username is the username to connect to the source
	Password     string `json:"password,omitempty"`     // Password is in CLEARTEXT
	SharedSecret string `json:"sharedSecret,omitempty"` // ShareSecret is the optional signing secret for Influx JWT authorization
	MetaURL      string `json:"metaUrl,omitempty"`      // MetaURL is the url for the meta node
	DefaultRP    string `json:"defaultRP"`              // DefaultRP is the default retention policy used in database queries to this source
	FluxURL      string `json:"fluxURL,omitempty"`      // FluxURL is the url for a flux connected to a 1x source
}

// SourceFields
type SourceFields struct {
	Token string `json:"token"` // Token is the 2.0 authorization token associated with a source
}

// SourceService is a service for managing sources.
type SourceService interface {
	// DefaultSource retrieves the default source.
	DefaultSource(ctx context.Context) (*Source, error)
	// FindSourceByID retrieves a source by its ID.
	FindSourceByID(ctx context.Context, id ID) (*Source, error)
	// FindSources returns a list of all sources.
	FindSources(ctx context.Context, opts FindOptions) ([]*Source, int, error)
	// CreateSource sets the sources ID and stores it.
	CreateSource(ctx context.Context, s *Source) error
	// UpdateSource updates the source.
	UpdateSource(ctx context.Context, id ID, upd SourceUpdate) (*Source, error)
	// DeleteSource removes the source.
	DeleteSource(ctx context.Context, id ID) error
}

// SourceUpdate represents updates to a source.
type SourceUpdate struct {
	Name               *string     `json:"name"`
	Type               *SourceType `json:"type,omitempty"`
	Token              *string     `json:"token"`
	URL                *string     `json:"url"`
	InsecureSkipVerify *bool       `json:"insecureSkipVerify,omitempty"`
	Telegraf           *string     `json:"telegraf"`
	Username           *string     `json:"username,omitempty"`
	Password           *string     `json:"password,omitempty"`
	SharedSecret       *string     `json:"sharedSecret,omitempty"`
	MetaURL            *string     `json:"metaURL,omitempty"`
	FluxURL            *string     `json:"fluxURL,omitempty"`
	Role               *string     `json:"role,omitempty"`
	DefaultRP          *string     `json:"defaultRP"`
}

// Apply applies an update to a source.
func (u SourceUpdate) Apply(s *Source) error {
	if u.Name != nil {
		s.Name = *u.Name
	}
	if u.Type != nil {
		s.Type = *u.Type
	}
	if u.Token != nil {
		s.Token = *u.Token
	}
	if u.URL != nil {
		s.URL = *u.URL
	}
	if u.InsecureSkipVerify != nil {
		s.InsecureSkipVerify = *u.InsecureSkipVerify
	}
	if u.Telegraf != nil {
		s.Telegraf = *u.Telegraf
	}
	if u.Username != nil {
		s.Username = *u.Username
	}
	if u.Password != nil {
		s.Password = *u.Password
	}
	if u.SharedSecret != nil {
		s.SharedSecret = *u.SharedSecret
	}
	if u.MetaURL != nil {
		s.MetaURL = *u.MetaURL
	}
	if u.FluxURL != nil {
		s.FluxURL = *u.FluxURL
	}
	if u.DefaultRP != nil {
		s.DefaultRP = *u.DefaultRP
	}

	return nil
}
func (s *Source) FindBucketByID(ctx context.Context, id ID) (*Bucket, error) {
	if s.BucketService == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.BucketService.FindBucketByID(ctx, id)
}

func (s *Source) FindBucket(ctx context.Context, filter BucketFilter) (*Bucket, error) {
	if s.BucketService == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.BucketService.FindBucket(ctx, filter)
}

func (s *Source) FindBuckets(ctx context.Context, filter BucketFilter, opt ...FindOptions) ([]*Bucket, int, error) {
	if s.BucketService == nil {
		return nil, 0, fmt.Errorf("not supported")
	}
	return s.BucketService.FindBuckets(ctx, filter, opt...)
}

func (s *Source) CreateBucket(ctx context.Context, b *Bucket) error {
	if s.BucketService == nil {
		return fmt.Errorf("not supported")
	}
	return s.BucketService.CreateBucket(ctx, b)
}

func (s *Source) UpdateBucket(ctx context.Context, id ID, upd BucketUpdate) (*Bucket, error) {
	if s.BucketService == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.BucketService.UpdateBucket(ctx, id, upd)
}

func (s *Source) DeleteBucket(ctx context.Context, id ID) error {
	if s.BucketService == nil {
		return fmt.Errorf("not supported")
	}
	return s.BucketService.DeleteBucket(ctx, id)
}

func (s *Source) Query(ctx context.Context, q *SourceQuery) (*SourceQueryResult, error) {
	if s.SourceQuerier == nil {
		return nil, fmt.Errorf("not supported")
	}
	return s.SourceQuerier.Query(ctx, q)
}
<|endoftext|>


<commit_before>package raft

// StableStore is used to provide stable storage
// of key configurations to ensure safety.
type StableStore interface {
	// Returns the current term
	CurrentTerm() (uint64, error)

	// Returns the candidate we voted for this term
	VotedFor() (string, error)

	// Sets the current term. Clears the current vote.
	SetCurrentTerm(uint64) error

	// Sets a candidate vote for the current term
	SetVote(string) error

	// Returns our candidate ID. This should be unique
	// and constant across runs
	CandidateID() (string, error)
}
<commit_msg>simplify the stable storage interface, push to higher level<commit_after>package raft

// StableStore is used to provide stable storage
// of key configurations to ensure safety.
type StableStore interface {
	Set(key []byte, val []byte) error
	Get(key []byte) ([]byte, error)

	SetUint64(key []byte, val uint64) error
	GetUint64(key []byte) (uint64, error)
}
<|endoftext|>


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

package main

import (
	"github.com/ctdk/goiardi/actor"
	"github.com/ctdk/goiardi/node"
	"net/http"
	"encoding/json"
)

func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}
	if !opUser.IsAdmin() {
		jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
		return
	}
	pathArray := splitPath(r.URL.Path)

	if len(pathArray) < 3 {
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}

	var statusResponse interface{}

	switch r.Method {
	case "GET":
		/* pathArray[1] will tell us what operation we're doing */
		switch pathArray[1] {
			case "all":

			case "node":
				if len(pathArray) != 4 {
					jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
					return
				}
				nodeName := pathArray[2]
				op := pathArray[3]
				n, gerr := node.Get(nodeName)
				if gerr != nil {
					jsonErrorReport(w, r, gerr.Error(), gerr.Status())
					return
				}
				switch op {
				case "latest":
					ns, err := n.LatestStatus()
					if err != nil {
						jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
						return
					}
					statusResponse = ns.ToJSON()
				case "all":
					ns, err := n.AllStatuses()
					if err != nil {
						jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
						return
					}
					sr := make([]map[string]string, len(ns))
					for i, v := range ns {
						sr[i] = v.ToJSON()
					}
					statusResponse = sr
				default:
					jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
					return
				}
			default:
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
		}
	default:
		jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(&statusResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
<commit_msg>node status available over http<commit_after>/*
 * Copyright (c) 2013-2014, Jeremy Bingham (<jbingham@gmail.com>)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"github.com/ctdk/goiardi/actor"
	"github.com/ctdk/goiardi/node"
	"github.com/ctdk/goiardi/util"
	"net/http"
	"encoding/json"
	"fmt"
)

func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	opUser, oerr := actor.GetReqUser(r.Header.Get("X-OPS-USERID"))
	if oerr != nil {
		jsonErrorReport(w, r, oerr.Error(), oerr.Status())
		return
	}
	if !opUser.IsAdmin() {
		jsonErrorReport(w, r, "You must be an admin to do that", http.StatusForbidden)
		return
	}
	pathArray := splitPath(r.URL.Path)

	if len(pathArray) < 3 {
		jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
		return
	}

	var statusResponse interface{}

	switch r.Method {
	case "GET":
		/* pathArray[1] will tell us what operation we're doing */
		switch pathArray[1] {
			// /status/all/nodes
			case "all":
				if len(pathArray) != 3 {
					jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
					return
				}
				if pathArray[2] != "nodes" {
					jsonErrorReport(w, r, "Invalid object to get status for", http.StatusBadRequest)
					return
				}
				nodes := node.AllNodes()
				sr := make([]map[string]string, len(nodes))
				for i, n := range nodes {
					ns, err := n.LatestStatus()
					if err != nil {
						nsbad := make(map[string]string)
						nsbad["node_name"] = n.Name
						nsbad["status"] = "no record"
						sr[i] = nsbad
						continue
					}
					sr[i] = ns.ToJSON()
					nsurl := fmt.Sprintf("/status/node/%s/latest", n.Name)
					sr[i]["url"] = util.CustomURL(nsurl)
				}
				statusResponse = sr
			// /status/node/<nodeName>/(all|latest)
			case "node":
				if len(pathArray) != 4 {
					jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
					return
				}
				nodeName := pathArray[2]
				op := pathArray[3]
				n, gerr := node.Get(nodeName)
				if gerr != nil {
					jsonErrorReport(w, r, gerr.Error(), gerr.Status())
					return
				}
				switch op {
				case "latest":
					ns, err := n.LatestStatus()
					if err != nil {
						jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
						return
					}
					statusResponse = ns.ToJSON()
				case "all":
					ns, err := n.AllStatuses()
					if err != nil {
						jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
						return
					}
					sr := make([]map[string]string, len(ns))
					for i, v := range ns {
						sr[i] = v.ToJSON()
					}
					statusResponse = sr
				default:
					jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
					return
				}
			default:
				jsonErrorReport(w, r, "Bad request", http.StatusBadRequest)
				return
		}
	default:
		jsonErrorReport(w, r, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(&statusResponse); err != nil {
		jsonErrorReport(w, r, err.Error(), http.StatusInternalServerError)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

type Target struct {
	url           string
	method        string
	total         int
	fail          int
	lastError     string
	lastIsSuccess bool
	lastTime      time.Time
}

func main() {
	gin.SetMode(gin.ReleaseMode)
	now := time.Now()
	targets := []Target{
		Target{"https://yorkyao.xyz/api/version", "GET", 0, 0, "", true, now},
		Target{"https://yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"https://doc.yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"https://news.yorkyao.xyz/items", "GET", 0, 0, "", true, now},
		Target{"https://robot.yorkyao.xyz/", "POST", 0, 0, "", true, now},
		Target{"https://upload.yorkyao.xyz/api/temperary", "POST", 0, 0, "", true, now},
	}

	ticker := time.NewTicker(time.Second * 60)

	go func() {
		for t := range ticker.C {
			fmt.Println(t)
			fmt.Println(targets)
			for i := 0; i < len(targets); i++ {
				var resp *http.Response
				var err error
				if targets[i].method == "GET" {
					resp, err = http.Get(targets[i].url)
				} else {
					resp, err = http.Post(targets[i].url, "application/x-www-form-urlencoded", nil)
				}
				targets[i].total++
				targets[i].lastTime = time.Now()
				if err == nil && resp.StatusCode < 500 {
					targets[i].lastIsSuccess = true
				} else {
					targets[i].fail++
					targets[i].lastError = err.Error()
					targets[i].lastIsSuccess = false
					defer resp.Body.Close()
				}
			}
		}
	}()

	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		result := ""
		for i := 0; i < len(targets); i++ {
			target := targets[i]
			if target.lastIsSuccess == false {
				result += target.url + ": fail\n"
			} else {
				result += target.url + ": success\n"
			}
		}
		c.String(200, result)
	})
	address := "localhost:9992"
	fmt.Println("listening: " + address)
	r.Run(address)
}
<commit_msg>OK, got the trick of json marshal<commit_after>package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

type Target struct {
	URL           string    `json:"url"`
	Method        string    `json:"method"`
	Total         int       `json:"total"`
	Fail          int       `json:"fail"`
	LastError     string    `json:"lastError"`
	LastIsSuccess bool      `json:"lastIsSuccess"`
	LastTime      time.Time `json:"lastTime"`
}

func main() {
	gin.SetMode(gin.ReleaseMode)
	now := time.Now()
	targets := []Target{
		Target{"https://yorkyao.xyz/api/version", "GET", 0, 0, "", true, now},
		Target{"https://yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"https://doc.yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"https://news.yorkyao.xyz/items", "GET", 0, 0, "", true, now},
		Target{"https://robot.yorkyao.xyz/", "POST", 0, 0, "", true, now},
		Target{"https://upload.yorkyao.xyz/api/temperary", "POST", 0, 0, "", true, now},
	}

	ticker := time.NewTicker(time.Second * 60)

	go func() {
		for t := range ticker.C {
			fmt.Println(t)
			fmt.Println(targets)
			for i := 0; i < len(targets); i++ {
				var resp *http.Response
				var err error
				if targets[i].Method == "GET" {
					resp, err = http.Get(targets[i].URL)
				} else {
					resp, err = http.Post(targets[i].URL, "application/x-www-form-urlencoded", nil)
				}
				targets[i].Total++
				targets[i].LastTime = time.Now()
				if err == nil && resp.StatusCode < 500 {
					targets[i].LastIsSuccess = true
				} else {
					targets[i].Fail++
					targets[i].LastError = err.Error()
					targets[i].LastIsSuccess = false
					defer resp.Body.Close()
				}
			}
		}
	}()

	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, targets)
	})
	address := "localhost:9992"
	fmt.Println("listening: " + address)
	r.Run(address)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

type Target struct {
	Name          string    `json:"name"`
	URL           string    `json:"url"`
	Method        string    `json:"method"`
	Total         int       `json:"total"`
	Fail          int       `json:"fail"`
	LastError     string    `json:"lastError"`
	LastIsSuccess bool      `json:"lastIsSuccess"`
	LastTime      time.Time `json:"lastTime"`
}

func main() {
	gin.SetMode(gin.ReleaseMode)
	now := time.Now()
	targets := []Target{
		Target{"subsnoti backend", "https://yorkyao.xyz/api/version", "GET", 0, 0, "", true, now},
		Target{"subsnoti frontend", "https://yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"subsnoti doc", "https://doc.yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"news fetcher", "https://news.yorkyao.xyz/items", "GET", 0, 0, "", true, now},
		Target{"deploy robot", "https://robot.yorkyao.xyz/", "POST", 0, 0, "", true, now},
		Target{"subsnoti upload", "https://upload.yorkyao.xyz/api/temperary", "POST", 0, 0, "", true, now},
	}

	ticker := time.NewTicker(time.Second * 60)

	go func() {
		for t := range ticker.C {
			fmt.Println(t)
			fmt.Println(targets)
			for i := 0; i < len(targets); i++ {
				var resp *http.Response
				var err error
				if targets[i].Method == "GET" {
					resp, err = http.Get(targets[i].URL)
				} else {
					resp, err = http.Post(targets[i].URL, "application/x-www-form-urlencoded", nil)
				}
				targets[i].Total++
				targets[i].LastTime = time.Now()
				if err == nil && resp.StatusCode < 500 {
					targets[i].LastIsSuccess = true
				} else {
					targets[i].Fail++
					targets[i].LastError = err.Error()
					targets[i].LastIsSuccess = false
					defer resp.Body.Close()
				}
			}
		}
	}()

	r := gin.Default()
	r.GET("/api/status", func(c *gin.Context) {
		c.JSON(200, targets)
	})
	address := "localhost:9992"
	fmt.Println("listening: " + address)
	r.Run(address)
}
<commit_msg>fix bug of invalid memory address or nil pointer dereference<commit_after>package main

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
)

type Target struct {
	Name          string    `json:"name"`
	URL           string    `json:"url"`
	Method        string    `json:"method"`
	Total         int       `json:"total"`
	Fail          int       `json:"fail"`
	LastError     string    `json:"lastError"`
	LastIsSuccess bool      `json:"lastIsSuccess"`
	LastTime      time.Time `json:"lastTime"`
}

func main() {
	gin.SetMode(gin.ReleaseMode)
	now := time.Now()
	targets := []Target{
		Target{"subsnoti backend", "https://yorkyao.xyz/api/version", "GET", 0, 0, "", true, now},
		Target{"subsnoti frontend", "https://yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"subsnoti doc", "https://doc.yorkyao.xyz/", "GET", 0, 0, "", true, now},
		Target{"news fetcher", "https://news.yorkyao.xyz/items", "GET", 0, 0, "", true, now},
		Target{"deploy robot", "https://robot.yorkyao.xyz/", "POST", 0, 0, "", true, now},
		Target{"subsnoti upload", "https://upload.yorkyao.xyz/api/temperary", "POST", 0, 0, "", true, now},
	}

	ticker := time.NewTicker(time.Second * 60)

	go func() {
		for t := range ticker.C {
			fmt.Println(t)
			fmt.Println(targets)
			for i := 0; i < len(targets); i++ {
				var resp *http.Response
				var err error
				if targets[i].Method == "GET" {
					resp, err = http.Get(targets[i].URL)
				} else {
					resp, err = http.Post(targets[i].URL, "application/x-www-form-urlencoded", nil)
				}
				targets[i].Total++
				targets[i].LastTime = time.Now()
				if err != nil {
					targets[i].Fail++
					targets[i].LastError = err.Error()
					targets[i].LastIsSuccess = false
				} else if resp != nil && resp.StatusCode >= 500 {
					targets[i].Fail++
					targets[i].LastError = strconv.Itoa(resp.StatusCode)
					targets[i].LastIsSuccess = false
				} else {
					targets[i].LastIsSuccess = true
				}
				if resp != nil {
					defer resp.Body.Close()
				}
			}
		}
	}()

	r := gin.Default()
	r.GET("/api/status", func(c *gin.Context) {
		c.JSON(200, targets)
	})
	address := "localhost:9992"
	fmt.Println("listening: " + address)
	r.Run(address)
}
<|endoftext|>


<commit_before>package httpie

import (
    "net/http"
    "bufio"
    "fmt"
    "errors"
)

func NewStream(endpoint Endpoint, auth Authorizer, consumer Consumer) *Stream {
    return &Stream{
        endpoint:   endpoint,
        authorizer: auth,
        consumer:   consumer,
        data:       make(chan []byte, 50),
    }
}

type Stream struct {
    data        chan []byte
    endpoint    Endpoint
    authorizer  Authorizer
    consumer    Consumer
}

func (s *Stream) Start() {
    resp, err := s.connect()
    if err != nil {
        return
    }

    s.consume(resp)
}

func (s *Stream) Data() (chan []byte) {
    return s.data
}

func (s *Stream) connect() (*http.Response, error) {
    client := &http.Client{}
    req    := &http.Request{Header: http.Header{}}

    s.endpoint.ApplyTo(req)
    if s.authorizer != nil {
        s.authorizer.Authorize(req)
    }

    resp, err := client.Do(req)

    if err != nil {
        return nil, err
    }

    if resp.StatusCode != 200 {
        return nil, errors.New(fmt.Sprintf("Status code received: %s", resp.StatusCode))
    }

    return resp, nil
}

func (s *Stream) consume(resp *http.Response) {
    reader := bufio.NewReader(resp.Body)

    var (
        b []byte
        err error
    )

    for {
        b, err = s.consumer.Consume(reader)

        if err != nil {
            resp.Body.Close()

            if resp, err = s.connect(); err != nil {
                continue
            }

            reader = bufio.NewReader(resp.Body)
        }

        s.data <- b
    }
}
<commit_msg>Name this Connect instead of Start<commit_after>package httpie

import (
    "net/http"
    "bufio"
    "fmt"
    "errors"
)

func NewStream(endpoint Endpoint, auth Authorizer, consumer Consumer) *Stream {
    return &Stream{
        endpoint:   endpoint,
        authorizer: auth,
        consumer:   consumer,
        data:       make(chan []byte, 50),
    }
}

type Stream struct {
    data        chan []byte
    endpoint    Endpoint
    authorizer  Authorizer
    consumer    Consumer
}

func (s *Stream) Connect() {
    resp, err := s.connect()
    if err != nil {
        return
    }

    s.consume(resp)
}

func (s *Stream) Data() (chan []byte) {
    return s.data
}

func (s *Stream) connect() (*http.Response, error) {
    client := &http.Client{}
    req    := &http.Request{Header: http.Header{}}

    s.endpoint.ApplyTo(req)
    if s.authorizer != nil {
        s.authorizer.Authorize(req)
    }

    resp, err := client.Do(req)

    if err != nil {
        return nil, err
    }

    if resp.StatusCode != 200 {
        return nil, errors.New(fmt.Sprintf("Status code received: %s", resp.StatusCode))
    }

    return resp, nil
}

func (s *Stream) consume(resp *http.Response) {
    reader := bufio.NewReader(resp.Body)

    var (
        b []byte
        err error
    )

    for {
        b, err = s.consumer.Consume(reader)

        if err != nil {
            resp.Body.Close()

            if resp, err = s.connect(); err != nil {
                continue
            }

            reader = bufio.NewReader(resp.Body)
        }

        s.data <- b
    }
}
<|endoftext|>


<commit_before>package twitter

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"time"
)

// StreamFilterParams represents the filter parameters used in a stream.
// https://dev.twitter.com/streaming/overview/request-parameters
type StreamFilterParams struct {
	FilterLevel   string
	Follow        []string
	Language      []string
	Locations     []string
	StallWarnings bool
	Track         []string
}

// StreamMessage represents a message received from a Twitter stream. Fields
// should be checked for existence before being used.
type StreamMessage struct {
	*Tweet
	Delete         *DeleteMessage         `json:"delete"`
	ScrubGeo       *ScrubGeoMessage       `json:"scrub_geo"`
	Limit          *LimitMessage          `json:"limit"`
	StatusWithheld *StatusWithheldMessage `json:"status_withheld"`
	UserWithheld   *UserWithheldMessage   `json:"user_withheld"`
	Disconnect     *DisconnectMessage     `json:"disconnect_message"`
}

// DeleteMessage represents a stream message that a given Tweet has been
// deleted.
type DeleteMessage struct {
	Status struct {
		ID        int64  `json:"id"`
		IDStr     string `json:"id_str"`
		UserID    int64  `json:"user_id"`
		UserIDStr string `json:"user_id_str"`
	} `json:"status"`
}

// ScrubGeoMessage represents a stream message that geolocated data must be
// stripped from a range of Tweets.
type ScrubGeoMessage struct {
	UserID          int64  `json:"user_id"`
	UserIDStr       string `json:"user_id_str"`
	UpToStatusID    int64  `json:"up_to_status_id"`
	UpToStatusIDStr string `json:"up_to_status_id_str"`
}

// LimitMessage represents a stream message that a filtered stream has matched
// more tweets than its current rate limit allows to be delivered.
type LimitMessage struct {
	Track int `json:"track"`
}

// StatusWithheldMessage represents a stream message that the indicated tweet
// has been withheld.
type StatusWithheldMessage struct {
	ID                  int64    `json:"id"`
	UserID              int64    `json:"user_id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}

// UserWithheldMessage represents a steram message that tweets from the
// indicated user have been withheld.
type UserWithheldMessage struct {
	ID                  int64    `json:"id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}

// DisconnectMessage represents a stream message that the stream will disconnect
// with the provided code and reason.
// https://dev.twitter.com/streaming/overview/messages-types#disconnect_messages
type DisconnectMessage struct {
	Code       int    `json:"code"`
	StreamName string `json:"stream_name"`
	Reason     string `json:"reason"`
}

// StreamErrFn represents a function that is called when an error is encountered
// in a stream and the connection will be retried. If the StreamErrFn returns
// a non-nil error, the stream will be immediately closed with the error.
type StreamErrFn func(Backoff, error) error

// Stream represents a Twitter stream connection. Messages from the stream can
// be read off the channel returned by Messages. At any point, the stream can be
// manually closed by calling the Close method. When the stream exits, the
// channel returned from the Done method will be closed.
type Stream struct {
	ctx    context.Context
	cancel context.CancelFunc

	client   OAuthClient
	values   url.Values
	endpoint string

	chMessage chan StreamMessage
	chDone    chan struct{}
	closeErr  error
	errFn     StreamErrFn
}

func newFilterStream(ctx context.Context, client OAuthClient, params StreamFilterParams, errFn StreamErrFn) *Stream {
	s := Stream{
		client:    client,
		values:    parseFilterParams(params),
		endpoint:  "https://stream.twitter.com/1.1/statuses/filter.json",
		chMessage: make(chan StreamMessage),
		chDone:    make(chan struct{}),
		errFn:     errFn,
	}
	s.ctx, s.cancel = context.WithCancel(ctx)
	go s.start()
	return &s
}

// Close immediately closes the stream and waits for the stream to completely
// close before returning the stream's shutdown error.
func (s *Stream) Close() error {
	s.cancel()
	<-s.chDone
	return s.Err()
}

// Done returns a channel that is closed when the stream has completely
// shutdown.
func (s *Stream) Done() <-chan struct{} {
	return s.chDone
}

// Err returns the stream's shutdown error after it has been closed. This should
// only be called after the the channel returned from Done has been closed.
func (s *Stream) Err() error {
	return s.closeErr
}

// Messages returns a read-only channel that messages are sent to as they are
// read off of the stream. Messages should be regualrly waiting on this channel,
// otherwise the stream's queue (on Twitter's end) will fill up and cause the
// stream to close.
func (s *Stream) Messages() <-chan StreamMessage {
	return s.chMessage
}

func (s *Stream) notifyError(boff Backoff, err error) error {
	if s.errFn == nil {
		return nil
	}
	return s.errFn(boff, err)
}

func (s *Stream) start() {
	var err error
	defer func() {
		s.cancel()
		s.closeErr = err
		close(s.chDone)
	}()

	boff := &backoff{}
	for {
		err = s.makeRequest(boff)
		select {
		case <-s.ctx.Done():
			err = s.ctx.Err()
			return
		default:
		}
		if err != nil {
			return
		}
		if d := boff.wait(); d > 0 {
			select {
			case <-s.ctx.Done():
				err = s.ctx.Err()
				return
			case <-time.After(d):
			}
		}
	}
}

func (s *Stream) makeRequest(boff *backoff) error {
	// Create a child context for this specific request.
	ctx, cancel := context.WithCancel(s.ctx)
	defer cancel()

	// Make HTTP request to open stream.
	resp, err := s.client.Do(ctx, "POST", nil, s.endpoint, s.values)
	if err != nil {
		boff.incNetDelay()
		return s.notifyError(boff, err)
	}
	defer resp.Body.Close()

	// Handle HTTP response.
	switch resp.StatusCode {
	case 200:
		err = s.readMessages(cancel, resp.Body)
		boff.reset()
		return s.notifyError(boff, err)
	case 401, 403, 404, 406, 413, 416:
		err = fmt.Errorf("%d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
		return err
	case 420:
		err = errors.New("420: Rate Limited")
		boff.incHTTPDelay(true)
		return s.notifyError(boff, err)
	default:
		err = fmt.Errorf("%d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
		boff.incHTTPDelay(false)
		return s.notifyError(boff, err)
	}
}

func (s *Stream) readMessages(cancel context.CancelFunc, r io.Reader) error {
	scanner := bufio.NewScanner(r)
	scanner.Split(scanLines)
	for {
		if err := s.readMessage(cancel, scanner); err != nil {
			return err
		}
	}
}

func (s *Stream) readMessage(cancel context.CancelFunc, scanner *bufio.Scanner) error {
	// Set 90 second timeout on receiving a message.
	// https://dev.twitter.com/streaming/overview/connecting
	t := time.AfterFunc(90*time.Second, func() { cancel() })
	// Scan next token.
	ok := scanner.Scan()
	t.Stop()
	if !ok {
		return scanner.Err()
	}
	b := scanner.Bytes()
	if len(b) == 0 || (len(b) == 1 && b[0] == '\n') {
		// Keep-alive.
		log.Println("Keep-alive")
		return nil
	}
	// Parse StreamMessage JSON.
	var sm StreamMessage
	err := json.Unmarshal(b, &sm)
	if err != nil {
		return err
	}
	select {
	case <-s.ctx.Done():
		return s.ctx.Err()
	case s.chMessage <- sm:
		return nil
	}
}

var newMsgBytes = []byte("\r\n")

func scanLines(data []byte, atEOF bool) (int, []byte, error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.Index(data, newMsgBytes); i >= 0 {
		return i + 2, data[0:i], nil
	}
	if atEOF {
		return len(data), data, nil
	}
	return 0, nil, nil
}

func parseFilterParams(params StreamFilterParams) url.Values {
	values := url.Values{}
	// Write possible settings.
	if params.FilterLevel != "" {
		values.Set("filter_level", params.FilterLevel)
	}
	if params.StallWarnings {
		values.Set("stall_warnings", "true")
	}
	// Write possible filters.
	var buf bytes.Buffer
	if len(params.Follow) > 0 {
		values.Set("follow", commaSeparated(&buf, params.Follow))
	}
	if len(params.Language) > 0 {
		values.Set("language", commaSeparated(&buf, params.Language))
	}
	if len(params.Locations) > 0 {
		values.Set("locations", commaSeparated(&buf, params.Locations))
	}
	if len(params.Track) > 0 {
		values.Set("track", commaSeparated(&buf, params.Track))
	}
	return values
}

func commaSeparated(buf *bytes.Buffer, ss []string) string {
	buf.Reset()
	for i, s := range ss {
		if i > 0 {
			buf.WriteByte(',')
		}
		buf.WriteString(s)
	}
	return buf.String()
}
<commit_msg>Move StartFilterStream to stream.go<commit_after>package twitter

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/url"
	"time"
)

// StartFilterStream starts and returns a new Stream using the provided context,
// stream filter parameters, and optional stream error callback.
func (c *Client) StartFilterStream(ctx context.Context, params StreamFilterParams, errFn StreamErrFn) *Stream {
	return newFilterStream(ctx, c, params, errFn)
}

// StreamFilterParams represents the filter parameters used in a stream.
// https://dev.twitter.com/streaming/overview/request-parameters
type StreamFilterParams struct {
	FilterLevel   string
	Follow        []string
	Language      []string
	Locations     []string
	StallWarnings bool
	Track         []string
}

// StreamMessage represents a message received from a Twitter stream. Fields
// should be checked for existence before being used.
type StreamMessage struct {
	*Tweet
	Delete         *DeleteMessage         `json:"delete"`
	ScrubGeo       *ScrubGeoMessage       `json:"scrub_geo"`
	Limit          *LimitMessage          `json:"limit"`
	StatusWithheld *StatusWithheldMessage `json:"status_withheld"`
	UserWithheld   *UserWithheldMessage   `json:"user_withheld"`
	Disconnect     *DisconnectMessage     `json:"disconnect_message"`
}

// DeleteMessage represents a stream message that a given Tweet has been
// deleted.
type DeleteMessage struct {
	Status struct {
		ID        int64  `json:"id"`
		IDStr     string `json:"id_str"`
		UserID    int64  `json:"user_id"`
		UserIDStr string `json:"user_id_str"`
	} `json:"status"`
}

// ScrubGeoMessage represents a stream message that geolocated data must be
// stripped from a range of Tweets.
type ScrubGeoMessage struct {
	UserID          int64  `json:"user_id"`
	UserIDStr       string `json:"user_id_str"`
	UpToStatusID    int64  `json:"up_to_status_id"`
	UpToStatusIDStr string `json:"up_to_status_id_str"`
}

// LimitMessage represents a stream message that a filtered stream has matched
// more tweets than its current rate limit allows to be delivered.
type LimitMessage struct {
	Track int `json:"track"`
}

// StatusWithheldMessage represents a stream message that the indicated tweet
// has been withheld.
type StatusWithheldMessage struct {
	ID                  int64    `json:"id"`
	UserID              int64    `json:"user_id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}

// UserWithheldMessage represents a steram message that tweets from the
// indicated user have been withheld.
type UserWithheldMessage struct {
	ID                  int64    `json:"id"`
	WithheldInCountries []string `json:"withheld_in_countries"`
}

// DisconnectMessage represents a stream message that the stream will disconnect
// with the provided code and reason.
// https://dev.twitter.com/streaming/overview/messages-types#disconnect_messages
type DisconnectMessage struct {
	Code       int    `json:"code"`
	StreamName string `json:"stream_name"`
	Reason     string `json:"reason"`
}

// StreamErrFn represents a function that is called when an error is encountered
// in a stream and the connection will be retried. If the StreamErrFn returns
// a non-nil error, the stream will be immediately closed with the error.
type StreamErrFn func(Backoff, error) error

// Stream represents a Twitter stream connection. Messages from the stream can
// be read off the channel returned by Messages. At any point, the stream can be
// manually closed by calling the Close method. When the stream exits, the
// channel returned from the Done method will be closed.
type Stream struct {
	ctx    context.Context
	cancel context.CancelFunc

	client   oauthClient
	values   url.Values
	endpoint string

	chMessage chan StreamMessage
	chDone    chan struct{}
	closeErr  error
	errFn     StreamErrFn
}

func newFilterStream(ctx context.Context, client oauthClient, params StreamFilterParams, errFn StreamErrFn) *Stream {
	s := Stream{
		client:    client,
		values:    parseFilterParams(params),
		endpoint:  "https://stream.twitter.com/1.1/statuses/filter.json",
		chMessage: make(chan StreamMessage),
		chDone:    make(chan struct{}),
		errFn:     errFn,
	}
	s.ctx, s.cancel = context.WithCancel(ctx)
	go s.start()
	return &s
}

// Close immediately closes the stream and waits for the stream to completely
// close before returning the stream's shutdown error.
func (s *Stream) Close() error {
	s.cancel()
	<-s.chDone
	return s.Err()
}

// Done returns a channel that is closed when the stream has completely
// shutdown.
func (s *Stream) Done() <-chan struct{} {
	return s.chDone
}

// Err returns the stream's shutdown error after it has been closed. This should
// only be called after the the channel returned from Done has been closed.
func (s *Stream) Err() error {
	return s.closeErr
}

// Messages returns a read-only channel that messages are sent to as they are
// read off of the stream. Messages should be regualrly waiting on this channel,
// otherwise the stream's queue (on Twitter's end) will fill up and cause the
// stream to close.
func (s *Stream) Messages() <-chan StreamMessage {
	return s.chMessage
}

func (s *Stream) notifyError(boff Backoff, err error) error {
	if s.errFn == nil {
		return nil
	}
	return s.errFn(boff, err)
}

func (s *Stream) start() {
	defer func() {
		s.cancel()
		close(s.chDone)
	}()

	boff := &backoff{}
	for {
		s.closeErr = s.makeRequest(boff)
		select {
		case <-s.ctx.Done():
			s.closeErr = s.ctx.Err()
			return
		default:
		}
		if s.closeErr != nil {
			return
		}
		if d := boff.wait(); d > 0 {
			select {
			case <-s.ctx.Done():
				s.closeErr = s.ctx.Err()
				return
			case <-time.After(d):
			}
		}
	}
}

func (s *Stream) makeRequest(boff *backoff) error {
	// Create a child context for this specific request.
	ctx, cancel := context.WithCancel(s.ctx)
	defer cancel()

	// Make HTTP request to open stream.
	resp, err := s.client.do(ctx, "POST", s.endpoint, s.values)
	if err != nil {
		boff.incNetDelay()
		return s.notifyError(boff, err)
	}
	defer resp.Body.Close()

	// Handle HTTP response.
	switch resp.StatusCode {
	case 200:
		err = s.readMessages(cancel, resp.Body)
		boff.reset()
		return s.notifyError(boff, err)
	case 401, 403, 404, 406, 413, 416:
		err = fmt.Errorf("%d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
		return err
	case 420:
		err = errors.New("420: Rate Limited")
		boff.incHTTPDelay(true)
		return s.notifyError(boff, err)
	default:
		err = fmt.Errorf("%d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
		boff.incHTTPDelay(false)
		return s.notifyError(boff, err)
	}
}

func (s *Stream) readMessages(cancel context.CancelFunc, r io.Reader) error {
	scanner := bufio.NewScanner(r)
	scanner.Split(scanLines)
	for {
		if err := s.readMessage(cancel, scanner); err != nil {
			return err
		}
	}
}

func (s *Stream) readMessage(cancel context.CancelFunc, scanner *bufio.Scanner) error {
	// Set 90 second timeout on receiving a message.
	// https://dev.twitter.com/streaming/overview/connecting
	t := time.AfterFunc(90*time.Second, func() { cancel() })
	ok := scanner.Scan()
	t.Stop()
	if !ok {
		return scanner.Err()
	}
	b := scanner.Bytes()
	if len(b) == 0 || (len(b) == 1 && b[0] == '\n') {
		// Keep-alive.
		log.Println("Keep-alive")
		return nil
	}
	// Parse StreamMessage JSON.
	var sm StreamMessage
	err := json.Unmarshal(b, &sm)
	if err != nil {
		return err
	}
	select {
	case <-s.ctx.Done():
		return s.ctx.Err()
	case s.chMessage <- sm:
		return nil
	}
}

var newMsgBytes = []byte("\r\n")

func scanLines(data []byte, atEOF bool) (int, []byte, error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.Index(data, newMsgBytes); i >= 0 {
		return i + 2, data[0:i], nil
	}
	if atEOF {
		return len(data), data, nil
	}
	return 0, nil, nil
}

func parseFilterParams(params StreamFilterParams) url.Values {
	values := url.Values{}
	// Write possible settings.
	if params.FilterLevel != "" {
		values.Set("filter_level", params.FilterLevel)
	}
	if params.StallWarnings {
		values.Set("stall_warnings", "true")
	}
	// Write possible filters.
	var buf bytes.Buffer
	if len(params.Follow) > 0 {
		values.Set("follow", commaSeparated(&buf, params.Follow))
	}
	if len(params.Language) > 0 {
		values.Set("language", commaSeparated(&buf, params.Language))
	}
	if len(params.Locations) > 0 {
		values.Set("locations", commaSeparated(&buf, params.Locations))
	}
	if len(params.Track) > 0 {
		values.Set("track", commaSeparated(&buf, params.Track))
	}
	return values
}

func commaSeparated(buf *bytes.Buffer, ss []string) string {
	buf.Reset()
	for i, s := range ss {
		if i > 0 {
			buf.WriteByte(',')
		}
		buf.WriteString(s)
	}
	return buf.String()
}
<|endoftext|>


<commit_before>// Package stripe provides the binding for Stripe REST APIs.
package stripe

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// defaultUrl is the public Stripe URL for APIs.
const defaultUrl = "https://api.stripe.com/v1"

// apiversion is the currently supported API version
const apiversion = "2014-08-20"

// clientversion is the binding version
const clientversion = "1.0"

// Backend is an interface for making calls against a Stripe service.
// This interface exists to enable mocking for during testing if needed.
type Backend interface {
	Call(method, path, token string, body *url.Values, v interface{}) error
}

// InternalBackend is the internal implementation for making HTTP calls to Stripe.
type InternalBackend struct {
	url        string
	httpClient *http.Client
}

// NewInternalBackend returns a customized backend used for making calls in this binding.
// This method should be called in one of two scenarios:
//	1. You're running in a Google AppEngine environment where the http.DefaultClient is not available.
//  2. You're doing internal development at Stripe.
func NewInternalBackend(httpClient *http.Client, url string) *InternalBackend {
	if len(url) == 0 {
		url = defaultUrl
	}

	return &InternalBackend{
		url:        url,
		httpClient: httpClient,
	}
}

// Key is the Stripe API key used globally in the binding.
var Key string

var debug bool
var backend Backend

// SetDebug enables additional tracing globally.
// The method is designed for used during testing.
func SetDebug(value bool) {
	debug = value
}

// GetBackend returns the currently used backend in the binding.
func GetBackend() Backend {
	if backend == nil {
		backend = NewInternalBackend(http.DefaultClient, "")
	}

	return backend
}

// SetBackend sets the backend used in the binding.
func SetBackend(b Backend) {
	backend = b
}

// Call is the Backend.Call implementation for invoking Stripe APIs.
func (s *InternalBackend) Call(method, path, token string, body *url.Values, v interface{}) error {
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	path = s.url + path

	if body != nil && len(*body) > 0 {
		path += "?" + body.Encode()
	}

	req, err := http.NewRequest(method, path, nil)
	if err != nil {
		log.Printf("Cannot create Stripe request: %v\n", err)
		return err
	}

	req.SetBasicAuth(token, "")
	req.Header.Add("Stripe-Version", apiversion)
	req.Header.Add("X-Stripe-Client-User-Agent", "Stripe.Go-"+clientversion)

	log.Printf("Requesting %v %q\n", method, path)
	start := time.Now()

	res, err := s.httpClient.Do(req)

	if debug {
		log.Printf("Completed in %v\n", time.Since(start))
	}

	if err != nil {
		log.Printf("Request to Stripe failed: %v\n", err)
		return err
	}
	defer res.Body.Close()

	resBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("Cannot parse Stripe response: %v\n", err)
		return err
	}

	if res.StatusCode >= 400 {
		// for some odd reason, the Erro structure doesn't unmarshal
		// initially I thought it was because it's a struct inside of a struct
		// but even after trying that, it still didn't work
		// so unmarshalling to a map for now and parsing the results manually
		// but should investigate later
		var errMap map[string]interface{}
		json.Unmarshal(resBody, &errMap)

		if e, found := errMap["error"]; !found {
			err := errors.New(string(resBody))
			log.Printf("Unparsable error returned from Stripe: %v\n", err)
			return err
		} else {
			root := e.(map[string]interface{})
			err := &Error{
				Type:           ErrorType(root["type"].(string)),
				Msg:            root["message"].(string),
				HttpStatusCode: res.StatusCode,
			}

			if code, found := root["code"]; found {
				err.Code = ErrorCode(code.(string))
			}

			if param, found := root["param"]; found {
				err.Param = param.(string)
			}

			log.Printf("Error encountered from Stripe: %v\n", err)
			return err
		}
	}

	if debug {
		log.Printf("Stripe Response: %q\n", resBody)
	}

	if v != nil {
		return json.Unmarshal(resBody, v)
	}

	return nil
}
<commit_msg>use the proper header for user agent<commit_after>// Package stripe provides the binding for Stripe REST APIs.
package stripe

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// defaultUrl is the public Stripe URL for APIs.
const defaultUrl = "https://api.stripe.com/v1"

// apiversion is the currently supported API version
const apiversion = "2014-08-20"

// clientversion is the binding version
const clientversion = "1.0"

// Backend is an interface for making calls against a Stripe service.
// This interface exists to enable mocking for during testing if needed.
type Backend interface {
	Call(method, path, token string, body *url.Values, v interface{}) error
}

// InternalBackend is the internal implementation for making HTTP calls to Stripe.
type InternalBackend struct {
	url        string
	httpClient *http.Client
}

// NewInternalBackend returns a customized backend used for making calls in this binding.
// This method should be called in one of two scenarios:
//	1. You're running in a Google AppEngine environment where the http.DefaultClient is not available.
//  2. You're doing internal development at Stripe.
func NewInternalBackend(httpClient *http.Client, url string) *InternalBackend {
	if len(url) == 0 {
		url = defaultUrl
	}

	return &InternalBackend{
		url:        url,
		httpClient: httpClient,
	}
}

// Key is the Stripe API key used globally in the binding.
var Key string

var debug bool
var backend Backend

// SetDebug enables additional tracing globally.
// The method is designed for used during testing.
func SetDebug(value bool) {
	debug = value
}

// GetBackend returns the currently used backend in the binding.
func GetBackend() Backend {
	if backend == nil {
		backend = NewInternalBackend(http.DefaultClient, "")
	}

	return backend
}

// SetBackend sets the backend used in the binding.
func SetBackend(b Backend) {
	backend = b
}

// Call is the Backend.Call implementation for invoking Stripe APIs.
func (s *InternalBackend) Call(method, path, token string, body *url.Values, v interface{}) error {
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}

	path = s.url + path

	if body != nil && len(*body) > 0 {
		path += "?" + body.Encode()
	}

	req, err := http.NewRequest(method, path, nil)
	if err != nil {
		log.Printf("Cannot create Stripe request: %v\n", err)
		return err
	}

	req.SetBasicAuth(token, "")
	req.Header.Add("Stripe-Version", apiversion)
	req.Header.Add("User-Agent", "Stripe.Go-"+clientversion)

	log.Printf("Requesting %v %q\n", method, path)
	start := time.Now()

	res, err := s.httpClient.Do(req)

	if debug {
		log.Printf("Completed in %v\n", time.Since(start))
	}

	if err != nil {
		log.Printf("Request to Stripe failed: %v\n", err)
		return err
	}
	defer res.Body.Close()

	resBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("Cannot parse Stripe response: %v\n", err)
		return err
	}

	if res.StatusCode >= 400 {
		// for some odd reason, the Erro structure doesn't unmarshal
		// initially I thought it was because it's a struct inside of a struct
		// but even after trying that, it still didn't work
		// so unmarshalling to a map for now and parsing the results manually
		// but should investigate later
		var errMap map[string]interface{}
		json.Unmarshal(resBody, &errMap)

		if e, found := errMap["error"]; !found {
			err := errors.New(string(resBody))
			log.Printf("Unparsable error returned from Stripe: %v\n", err)
			return err
		} else {
			root := e.(map[string]interface{})
			err := &Error{
				Type:           ErrorType(root["type"].(string)),
				Msg:            root["message"].(string),
				HttpStatusCode: res.StatusCode,
			}

			if code, found := root["code"]; found {
				err.Code = ErrorCode(code.(string))
			}

			if param, found := root["param"]; found {
				err.Param = param.(string)
			}

			log.Printf("Error encountered from Stripe: %v\n", err)
			return err
		}
	}

	if debug {
		log.Printf("Stripe Response: %q\n", resBody)
	}

	if v != nil {
		return json.Unmarshal(resBody, v)
	}

	return nil
}
<|endoftext|>


<commit_before>package triplestore

import (
	"fmt"
	"math/rand"
	"reflect"
	"time"
)

const (
	predTag = "predicate"
	subTag  = "subject"
)

var random = rand.New(rand.NewSource(time.Now().UnixNano()))

// Convert a Struct or ptr to Struct into triples
// using field tags.
// For each struct's field a triple is created:
// - Subject: function first argument
// - Predicate: tag value
// - Literal: actual field value according to field's type
// Unsupported types are ignored
func TriplesFromStruct(sub string, i interface{}) (out []Triple) {
	val := reflect.ValueOf(i)

	var ok bool
	val, ok = getStructOrPtrToStruct(val)
	if !ok {
		return
	}

	st := val.Type()

	for i := 0; i < st.NumField(); i++ {
		field, fVal := st.Field(i), val.Field(i)
		if !fVal.CanInterface() {
			continue
		}

		intValue := reflect.ValueOf(fVal.Interface())
		if intValue.Kind() == reflect.Ptr && intValue.IsNil() {
			continue
		}

		pred := field.Tag.Get(predTag)
		if tri, ok := buildTripleFromVal(sub, pred, fVal); ok {
			out = append(out, tri)
		}

		tag, embedded := field.Tag.Lookup(subTag)
		fVal, ok := getStructOrPtrToStruct(fVal)
		if ok && embedded {
			embedSub := tag
			if tag == "rand" {
				embedSub = fmt.Sprintf("%x", random.Uint32())
			}
			tris := TriplesFromStruct(embedSub, fVal.Interface())
			out = append(out, tris...)
			if embedPred, hasPred := field.Tag.Lookup(predTag); hasPred {
				out = append(out, SubjPred(sub, embedPred).Resource(embedSub))
			}
			continue
		}

		switch fVal.Kind() {
		case reflect.Slice:
			length := fVal.Len()
			for i := 0; i < length; i++ {
				sliceVal := fVal.Index(i)
				if tri, ok := buildTripleFromVal(sub, pred, sliceVal); ok {
					out = append(out, tri)
				}
			}
		}

	}

	return
}

func buildTripleFromVal(sub, pred string, v reflect.Value) (Triple, bool) {
	if !v.CanInterface() {
		return nil, false
	}
	if pred == "" {
		return nil, false
	}
	objLit, err := ObjectLiteral(v.Interface())
	if err != nil {
		return nil, false
	}

	return SubjPred(sub, pred).Object(objLit), true
}

func getStructOrPtrToStruct(v reflect.Value) (reflect.Value, bool) {
	switch v.Kind() {
	case reflect.Struct:
		return v, true
	case reflect.Ptr:
		if v.Elem().Kind() == reflect.Struct {
			return v.Elem(), true
		}
	}

	return v, false
}
<commit_msg>Seed random ID generator safe for concurrent use<commit_after>package triplestore

import (
	"fmt"
	"math/rand"
	"reflect"
	"time"
)

const (
	predTag = "predicate"
	subTag  = "subject"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// Convert a Struct or ptr to Struct into triples
// using field tags.
// For each struct's field a triple is created:
// - Subject: function first argument
// - Predicate: tag value
// - Literal: actual field value according to field's type
// Unsupported types are ignored
func TriplesFromStruct(sub string, i interface{}) (out []Triple) {
	val := reflect.ValueOf(i)

	var ok bool
	val, ok = getStructOrPtrToStruct(val)
	if !ok {
		return
	}

	st := val.Type()

	for i := 0; i < st.NumField(); i++ {
		field, fVal := st.Field(i), val.Field(i)
		if !fVal.CanInterface() {
			continue
		}

		intValue := reflect.ValueOf(fVal.Interface())
		if intValue.Kind() == reflect.Ptr && intValue.IsNil() {
			continue
		}

		pred := field.Tag.Get(predTag)
		if tri, ok := buildTripleFromVal(sub, pred, fVal); ok {
			out = append(out, tri)
		}

		tag, embedded := field.Tag.Lookup(subTag)
		fVal, ok := getStructOrPtrToStruct(fVal)
		if ok && embedded {
			embedSub := tag
			if tag == "rand" {
				embedSub = fmt.Sprintf("%x", rand.Uint32())
			}
			tris := TriplesFromStruct(embedSub, fVal.Interface())
			out = append(out, tris...)
			if embedPred, hasPred := field.Tag.Lookup(predTag); hasPred {
				out = append(out, SubjPred(sub, embedPred).Resource(embedSub))
			}
			continue
		}

		switch fVal.Kind() {
		case reflect.Slice:
			length := fVal.Len()
			for i := 0; i < length; i++ {
				sliceVal := fVal.Index(i)
				if tri, ok := buildTripleFromVal(sub, pred, sliceVal); ok {
					out = append(out, tri)
				}
			}
		}

	}

	return
}

func buildTripleFromVal(sub, pred string, v reflect.Value) (Triple, bool) {
	if !v.CanInterface() {
		return nil, false
	}
	if pred == "" {
		return nil, false
	}
	objLit, err := ObjectLiteral(v.Interface())
	if err != nil {
		return nil, false
	}

	return SubjPred(sub, pred).Object(objLit), true
}

func getStructOrPtrToStruct(v reflect.Value) (reflect.Value, bool) {
	switch v.Kind() {
	case reflect.Struct:
		return v, true
	case reflect.Ptr:
		if v.Elem().Kind() == reflect.Struct {
			return v.Elem(), true
		}
	}

	return v, false
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"time"
	"net/url"
	"path"
	"strings"

	"github.com/gorilla/mux"
	"github.com/philips/go-mailgun"
)

type config struct {
	Subscribegun struct {
		Hostname string
		Lists    []string
	}
	Mailgun struct {
		Key string
	}
}

var mg *mailgun.Client
var cfg config

type mail struct {
	from      string
	to        []string
	cc        []string
	bcc       []string
	subject   string
	html      string
	text      string
	headers   map[string]string
	options   map[string]string
	variables map[string]string
}

func (m *mail) From() string                 { return m.from }
func (m *mail) To() []string                 { return m.to }
func (m *mail) Cc() []string                 { return m.cc }
func (m *mail) Bcc() []string                { return m.bcc }
func (m *mail) Subject() string              { return m.subject }
func (m *mail) Html() string                 { return m.html }
func (m *mail) Text() string                 { return m.text }
func (m *mail) Headers() map[string]string   { return m.headers }
func (m *mail) Options() map[string]string   { return m.options }
func (m *mail) Variables() map[string]string { return m.variables }

func randomString(l int) string {
	bytes := make([]byte, l)
	for i := 0; i < l; i++ {
		bytes[i] = byte(randInt(65, 90))
	}
	return string(bytes)
}

func randInt(min int, max int) int {
	return min + rand.Intn(max-min)
}

// listAllowed checks to ensure that the list is in the configuration file as
// a publicly subscribable list.
func listAllowed(list string) bool {
	for _, l := range cfg.Subscribegun.Lists {
		if l == list {
			return true
		}
	}
	return false
}

// confirmationHandler handles a confirmation link and changes the persons
// subscription state to "subscribed" or "unsubscribed" if the token matches.
func confirmationHandler(w http.ResponseWriter, r *http.Request) {
	muxVars := mux.Vars(r)

	listName := muxVars["list"]
	if len(listName) == 0 {
		http.Error(w, "No list specified!", 404)
		return
	}

	email := muxVars["email"]
	if len(email) == 0 {
		http.Error(w, "No email address!", 400)
		return
	}

	token := muxVars["token"]
	if len(token) == 0 {
		http.Error(w, "No token!", 400)
		return
	}

	member, err := mg.GetListMember(listName, email)
	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}

	action := muxVars["action"]
	if token != member.Vars[strings.Title(action) + "Token"] {
		http.Error(w, "Bad confirmation token", 400)
		return
	}

	if action == "subscribe" {
		member.Subscribed = true
		fmt.Fprintf(w, "Success! You are now subscribed to %s", listName)
	} else if action == "unsubscribe" {
		member.Subscribed = false
		fmt.Fprintf(w, "Success! You are now unsubscribed from %s", listName)
	} else {
		http.Error(w, fmt.Sprintf("Unknown action %s", action), 500);
		return
	}

	_, err = mg.UpdateListMember(listName, member)
	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
	return
}

// initialHandler subscribes or unsubscribes the requested email from the list
// and sends a confirmation email with a token to ensure the email owner actually
// requested the action.
func initialHandler(w http.ResponseWriter, r *http.Request) {
	muxVars := mux.Vars(r)

	// Deal with CORS stuff
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
	w.Header().Add("Access-Control-Allow-Headers", "Origin")
	if w.Header().Get("access-control-request-headers") != "" {
		return
	}

	listName := muxVars["list"]
	if len(listName) == 0 {
		http.Error(w, "No list specified!", 404)
		return
	}

	if listAllowed(listName) == false {
		http.Error(w, "Unknown list.", 403)
		return
	}

	email := r.FormValue("email")

	action := muxVars["action"];

	switch action {
	case "subscribe": handleSubscribe(w, listName, email);
	case "unsubscribe": handleUnsubscribe(w, listName, email);
	default: http.Error(w, fmt.Sprintf("Unknown action %s", action), 500);
	}
}

// confirmURL generates a url.URL for a confirmation link that the user will
// get via and must click to confirm a request.
func confirmURL(action string, listName string, email string, key string) url.URL {
	u := url.URL{}
	u.Scheme = "http"
	u.Host = cfg.Subscribegun.Hostname
	u.Path = path.Join("/", action, listName, "confirm", email, key)
	return u
}

func addListMember(listName string, email string) (string, error) {
	// Generate the tokens for the user
	vars := map[string]string{
		"UnsubscribeToken": randomString(16),
		"SubscribeToken":   randomString(16),
	}
	member := mailgun.ListMember{email, false, vars, "", ""}
	key := vars["SubscribeToken"]

	_, err := mg.AddListMember(listName, member)
	return key, err
}

// handleSubscribe handles a subscribe request.
func handleSubscribe(w http.ResponseWriter, listName string, email string) {
	var key string

	member, err := mg.GetListMember(listName, email)
	// Try to add the member if it doesn't exist
	if err != nil {
		key, err = addListMember(listName, email)
	} else {
		key = member.Vars["SubscribeToken"]
	}

	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}

	u := confirmURL("subscribe", listName, email, key)
	confirmMail := mail{
		from:    "no-reply@lists.coreos.com",
		to:      []string{email},
		subject: "confirm subscription to " + listName,
		text:    "click here to confirm your subscription request to " + listName + ":\n" + u.String(),
	}
	_, err = mg.Send(&confirmMail)

	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
}

// handleUnsubscribe handles an unsubscribe request.
func handleUnsubscribe(w http.ResponseWriter, listName string, email string) {
	member, err := mg.GetListMember(listName, email)
	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
	key := member.Vars["UnsubscribeToken"]

	u := confirmURL("unsubscribe", listName, email, key)
	confirmMail := mail{
		from:    "no-reply@lists.coreos.com",
		to:      []string{email},
		subject: "confirm unsubscribe to " + listName,
		text:    "click here to confirm your unsubscribe request to " + listName + ":\n" + u.String(),
	}
	_, err = mg.Send(&confirmMail)

	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
}

func main() {
	configBytes, err := ioutil.ReadFile(os.Args[1])

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(configBytes, &cfg)
	if err != nil {
		panic(err)
	}

	mg = mailgun.New(cfg.Mailgun.Key)

	// TODO: add a secret seed in here
	rand.Seed(time.Now().UTC().UnixNano())

	r := mux.NewRouter()

	r.HandleFunc("/{action}/{list}", initialHandler)
	r.HandleFunc("/{action}/{list}/confirm/{email}/{token}", confirmationHandler)

	http.ListenAndServe(":8080", r)
}
<commit_msg>chore(subgun): go fmt<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"path"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/philips/go-mailgun"
)

type config struct {
	Subscribegun struct {
		Hostname string
		Lists    []string
	}
	Mailgun struct {
		Key string
	}
}

var mg *mailgun.Client
var cfg config

type mail struct {
	from      string
	to        []string
	cc        []string
	bcc       []string
	subject   string
	html      string
	text      string
	headers   map[string]string
	options   map[string]string
	variables map[string]string
}

func (m *mail) From() string                 { return m.from }
func (m *mail) To() []string                 { return m.to }
func (m *mail) Cc() []string                 { return m.cc }
func (m *mail) Bcc() []string                { return m.bcc }
func (m *mail) Subject() string              { return m.subject }
func (m *mail) Html() string                 { return m.html }
func (m *mail) Text() string                 { return m.text }
func (m *mail) Headers() map[string]string   { return m.headers }
func (m *mail) Options() map[string]string   { return m.options }
func (m *mail) Variables() map[string]string { return m.variables }

func randomString(l int) string {
	bytes := make([]byte, l)
	for i := 0; i < l; i++ {
		bytes[i] = byte(randInt(65, 90))
	}
	return string(bytes)
}

func randInt(min int, max int) int {
	return min + rand.Intn(max-min)
}

// listAllowed checks to ensure that the list is in the configuration file as
// a publicly subscribable list.
func listAllowed(list string) bool {
	for _, l := range cfg.Subscribegun.Lists {
		if l == list {
			return true
		}
	}
	return false
}

// confirmationHandler handles a confirmation link and changes the persons
// subscription state to "subscribed" or "unsubscribed" if the token matches.
func confirmationHandler(w http.ResponseWriter, r *http.Request) {
	muxVars := mux.Vars(r)

	listName := muxVars["list"]
	if len(listName) == 0 {
		http.Error(w, "No list specified!", 404)
		return
	}

	email := muxVars["email"]
	if len(email) == 0 {
		http.Error(w, "No email address!", 400)
		return
	}

	token := muxVars["token"]
	if len(token) == 0 {
		http.Error(w, "No token!", 400)
		return
	}

	member, err := mg.GetListMember(listName, email)
	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}

	action := muxVars["action"]
	if token != member.Vars[strings.Title(action)+"Token"] {
		http.Error(w, "Bad confirmation token", 400)
		return
	}

	if action == "subscribe" {
		member.Subscribed = true
		fmt.Fprintf(w, "Success! You are now subscribed to %s", listName)
	} else if action == "unsubscribe" {
		member.Subscribed = false
		fmt.Fprintf(w, "Success! You are now unsubscribed from %s", listName)
	} else {
		http.Error(w, fmt.Sprintf("Unknown action %s", action), 500)
		return
	}

	_, err = mg.UpdateListMember(listName, member)
	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
	return
}

// initialHandler subscribes or unsubscribes the requested email from the list
// and sends a confirmation email with a token to ensure the email owner actually
// requested the action.
func initialHandler(w http.ResponseWriter, r *http.Request) {
	muxVars := mux.Vars(r)

	// Deal with CORS stuff
	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
	w.Header().Add("Access-Control-Allow-Headers", "Origin")
	if w.Header().Get("access-control-request-headers") != "" {
		return
	}

	listName := muxVars["list"]
	if len(listName) == 0 {
		http.Error(w, "No list specified!", 404)
		return
	}

	if listAllowed(listName) == false {
		http.Error(w, "Unknown list.", 403)
		return
	}

	email := r.FormValue("email")

	action := muxVars["action"]

	switch action {
	case "subscribe":
		handleSubscribe(w, listName, email)
	case "unsubscribe":
		handleUnsubscribe(w, listName, email)
	default:
		http.Error(w, fmt.Sprintf("Unknown action %s", action), 500)
	}
}

// confirmURL generates a url.URL for a confirmation link that the user will
// get via and must click to confirm a request.
func confirmURL(action string, listName string, email string, key string) url.URL {
	u := url.URL{}
	u.Scheme = "http"
	u.Host = cfg.Subscribegun.Hostname
	u.Path = path.Join("/", action, listName, "confirm", email, key)
	return u
}

func addListMember(listName string, email string) (string, error) {
	// Generate the tokens for the user
	vars := map[string]string{
		"UnsubscribeToken": randomString(16),
		"SubscribeToken":   randomString(16),
	}
	member := mailgun.ListMember{email, false, vars, "", ""}
	key := vars["SubscribeToken"]

	_, err := mg.AddListMember(listName, member)
	return key, err
}

// handleSubscribe handles a subscribe request.
func handleSubscribe(w http.ResponseWriter, listName string, email string) {
	var key string

	member, err := mg.GetListMember(listName, email)
	// Try to add the member if it doesn't exist
	if err != nil {
		key, err = addListMember(listName, email)
	} else {
		key = member.Vars["SubscribeToken"]
	}

	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}

	u := confirmURL("subscribe", listName, email, key)
	confirmMail := mail{
		from:    "no-reply@lists.coreos.com",
		to:      []string{email},
		subject: "confirm subscription to " + listName,
		text:    "click here to confirm your subscription request to " + listName + ":\n" + u.String(),
	}
	_, err = mg.Send(&confirmMail)

	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
}

// handleUnsubscribe handles an unsubscribe request.
func handleUnsubscribe(w http.ResponseWriter, listName string, email string) {
	member, err := mg.GetListMember(listName, email)
	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
	key := member.Vars["UnsubscribeToken"]

	u := confirmURL("unsubscribe", listName, email, key)
	confirmMail := mail{
		from:    "no-reply@lists.coreos.com",
		to:      []string{email},
		subject: "confirm unsubscribe to " + listName,
		text:    "click here to confirm your unsubscribe request to " + listName + ":\n" + u.String(),
	}
	_, err = mg.Send(&confirmMail)

	if err != nil {
		http.Error(w, "Internal error", 500)
		fmt.Println(err)
		return
	}
}

func main() {
	configBytes, err := ioutil.ReadFile(os.Args[1])

	if err != nil {
		panic(err)
	}

	err = json.Unmarshal(configBytes, &cfg)
	if err != nil {
		panic(err)
	}

	mg = mailgun.New(cfg.Mailgun.Key)

	// TODO: add a secret seed in here
	rand.Seed(time.Now().UTC().UnixNano())

	r := mux.NewRouter()

	r.HandleFunc("/{action}/{list}", initialHandler)
	r.HandleFunc("/{action}/{list}/confirm/{email}/{token}", confirmationHandler)

	http.ListenAndServe(":8080", r)
}
<|endoftext|>