<commit_before>package socketio

import (
	"code.google.com/p/go.net/websocket"
	"net/http"
	"sync"
)

func init() {
	DefaultTransports.RegisterTransport(WebSocket)
}

var WebSocket = new(webSocket)

type webSocket struct {
	mutex       sync.Mutex
	session     *Session
	conn        *websocket.Conn
	isConnect   bool
	isOpen      bool
	waitForOpen chan bool
}

func (ws *webSocket) Name() string {
	return "websocket"
}

func (ws *webSocket) New(session *Session) Transport {
	return &webSocket{session: session}
}

func (ws *webSocket) webSocketHandler(conn *websocket.Conn) {
	ws.mutex.Lock()
	if ws.isOpen {
		ws.Close()
	}
	ws.conn = conn
	ws.isOpen = true
	ws.mutex.Unlock()
	ws.session.onOpen()
	for {
		var data string
		err := websocket.Message.Receive(conn, &data)
		if err != nil {
			ws.Close()
			return
		}
		ws.session.onFrame([]byte(data))
	}
}

func (ws *webSocket) OnData(w http.ResponseWriter, r *http.Request) {
	webSocketHandler := func(conn *websocket.Conn) {
		ws.webSocketHandler(conn)
	}
	websocket.Handler(webSocketHandler).ServeHTTP(w, r)
}

func (ws *webSocket) Send(data []byte) error {
	ws.mutex.Lock()
	defer ws.mutex.Unlock()
	return websocket.Message.Send(ws.conn, string(data))
}

func (ws *webSocket) Close() {
	ws.isOpen = false
	ws.conn.Close()
}
<commit_msg>send heartbeat<commit_after>package socketio

import (
	"code.google.com/p/go.net/websocket"
	"net"
	"net/http"
	"sync"
	"time"
)

func init() {
	DefaultTransports.RegisterTransport(WebSocket)
}

var WebSocket = new(webSocket)

type webSocket struct {
	mutex     sync.Mutex
	session   *Session
	conn      *websocket.Conn
	isConnect bool
	isOpen    bool
	heartBeat time.Duration
}

func (ws *webSocket) Name() string {
	return "websocket"
}

func (ws *webSocket) New(session *Session) Transport {
	ret := &webSocket{session: session}
	ret.heartBeat = time.Duration(session.server.heartbeatTimeout) * time.Second / 2
	return ret
}

func (ws *webSocket) webSocketHandler(conn *websocket.Conn) {
	ws.mutex.Lock()
	if ws.isOpen {
		ws.Close()
	}
	ws.conn = conn
	ws.isOpen = true
	ws.mutex.Unlock()
	ws.session.onOpen()
	for {
		var data string
		ws.conn.SetDeadline(time.Now().Add(ws.heartBeat))
		err := websocket.Message.Receive(conn, &data)
		if e, ok := err.(net.Error); ok && e.Timeout() {
			heartBeat := new(heartbeatPacket)
			err = ws.session.Of("").sendPacket(heartBeat)
			if err != nil {
				ws.Close()
				return
			}
			continue
		}
		if err != nil {
			ws.Close()
			return
		}
		ws.session.onFrame([]byte(data))
	}
}

func (ws *webSocket) OnData(w http.ResponseWriter, r *http.Request) {
	webSocketHandler := func(conn *websocket.Conn) {
		ws.webSocketHandler(conn)
	}
	websocket.Handler(webSocketHandler).ServeHTTP(w, r)
}

func (ws *webSocket) Send(data []byte) error {
	ws.mutex.Lock()
	defer ws.mutex.Unlock()
	ws.conn.SetDeadline(time.Now().Add(ws.heartBeat))
	return websocket.Message.Send(ws.conn, string(data))
}

func (ws *webSocket) Close() {
	ws.isOpen = false
	ws.conn.Close()
}
<|endoftext|>


<commit_before>package ari

import (
	"fmt"
	"sync"
	"time"

	"golang.org/x/net/context"
)

// RecordingStartTimeout is the amount of time to wait for a recording to start
// before declaring the recording to have failed.
var RecordingStartTimeout = 1 * time.Second

// LiveRecording describes a recording which is in progress
type LiveRecording struct {
	Cause       string `json:"cause,omitempty"`            // If failed, the cause of the failure
	DurationSec int    `json:"duration,omitempty"`         // Length of recording in seconds
	Format      string `json:"format"`                     // Format of recording (wav, gsm, etc)
	Name        string `json:"name"`                       // (base) name for the recording
	SilenceSec  int    `json:"silence_duration,omitempty"` // If silence was detected in the recording, the duration in seconds of that silence (requires that maxSilenceSeconds be non-zero)
	State       string `json:"state"`                      // Current state of the recording
	TalkingSec  int    `json:"talking_duration,omitempty"` // Duration of talking, in seconds, that has been detected in the recording (requires that maxSilenceSeconds be non-zero)
	TargetURI   string `json:"target_uri"`                 // URI for the channel or bridge which is being recorded (TODO: figure out format for this)

	client *Client // Reference to the client which created or returned this LiveRecording

	doneChan chan struct{} // channel for indicating the the recording is stopped.
	mu       sync.Mutex

	status int // The status of the live recording
}

// Duration returns the duration of the live recording, if known
func (l *LiveRecording) Duration() (dur time.Duration) {
	if l.DurationSec > 0 {
		dur = time.Duration(l.DurationSec) * time.Second
	}
	return
}

// SilenceDuration returns the duration of the detected silence
// during the recording, if known
func (l *LiveRecording) SilenceDuration() (dur time.Duration) {
	if l.SilenceSec > 0 {
		dur = time.Duration(l.SilenceSec) * time.Second
	}
	return
}

// TalkingDuration returns the duration of the detected talking
// during the recording, if known
func (l *LiveRecording) TalkingDuration() (dur time.Duration) {
	if l.TalkingSec > 0 {
		dur = time.Duration(l.TalkingSec) * time.Second
	}
	return
}

var (
	// ExistsFail indicates that a recording should fail if the
	// given name already exists.
	ExistsFail = "fail"

	// ExistsOverwrite indicates that if a recording exists of
	// the same name, it should be overwritten.
	ExistsOverwrite = "overwrite"

	// ExistsAppend indicates that if a recording exists of the
	// same name, it should be appended to.
	ExistsAppend = "append"
)

var (
	// TerminateNever indicates that a recording should not be
	// ended on any DTMF tone.
	TerminateNever = "none"

	// TerminateAny indicates that a recording should be terminated
	// if any DTMF digit is received
	TerminateAny = "any"

	// TerminateStar indicates that a recording should be terminated
	// if a * DTMF character is received.
	TerminateStar = "*"

	// TerminateHash indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminateHash = "#"

	// TerminatePound indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminatePound = "#"
)

const (
	// RecordInProgress indicates that a recording is still in progress
	RecordInProgress = iota

	// RecordCanceled indicates that a recording was canceled (by request)
	RecordCanceled

	// RecordFailed indicates that a recording failed
	RecordFailed

	// RecordFinished indicates that a recording finished normally
	RecordFinished

	// RecordHangup indicates that a recording was ended due to hangup
	RecordHangup
)

// RecordingOptions describes the set of options available when making a recording.
type RecordingOptions struct {
	// Format is the file format/encoding to which the recording should be stored.
	// This will usually be one of: slin, ulaw, alaw, wav, gsm.
	// If not specified, this will default to slin.
	Format string

	// MaxDuration is the maximum duration of the recording, after which the recording will
	// automatically stop.  If not set, there is no maximum.
	MaxDuration time.Duration

	// MaxSilence is the maximum duration of detected to be found before terminating the recording.
	MaxSilence time.Duration

	// Exists determines what should happen if the given recording already exists.
	// Valid values are: "fail", "overwrite", or "append".
	// If not specified, it will default to "fail"
	Exists string

	// Beep indicates whether a beep should be played to the recorded
	// party at the beginning of the recording.
	Beep bool

	// Terminate indicates whether the recording should be terminated on
	// receipt of a DTMF digit.
	// valid options are: "none", "any", "*", and "#"
	// If not specified, it will default to "none" (never terminate on DTMF).
	Terminate string
}

// ToRequest converts a set of recording options to a
// record request.
func (o *RecordingOptions) ToRequest(name string) *RecordRequest {
	if o.Format == "" {
		o.Format = "slin"
	}
	return &RecordRequest{
		Name:               name,
		Format:             o.Format,
		MaxDurationSeconds: int(o.MaxDuration.Seconds()),
		MaxSilenceSeconds:  int(o.MaxSilence.Seconds()),
		IfExists:           o.Exists,
		Beep:               o.Beep,
		TerminateOn:        o.Terminate,
	}
}

// StoredRecording describes a past recording which may be played back (via GetStoredRecording)
type StoredRecording struct {
	Format string `json:"format"`
	Name   string `json:"name"`

	client *Client // Reference to the client which created or returned this StoredRecording
}

// A Recorder is anything which can "Record"
type Recorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	GetClient() *Client
}

// A PlayerRecorder is anywhich which can Record and Play
type PlayerRecorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	Play(string) (string, error)
	GetClient() *Client
}

//ListStoredRecordings lists all completed recordings
//Equivalent to GET /recordings/stored
func (c *Client) ListStoredRecordings() ([]StoredRecording, error) {
	var m []StoredRecording
	err := c.Get("/recordings/stored", &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetStoredRecording returns a stored recording's details
//Equivalent to GET /recordings/stored/{recordingName}
func (c *Client) GetStoredRecording(recordingName string) (StoredRecording, error) {
	var m StoredRecording
	err := c.Get("/recordings/stored/"+recordingName, &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetLiveRecording returns a specific live recording
//Equivalent to GET /recordings/live/{recordingName}
func (c *Client) GetLiveRecording(recordingName string) (*LiveRecording, error) {
	var m LiveRecording
	err := c.Get("/recordings/live/"+recordingName, &m)
	return &m, err
}

// Record starts a recording on the given Recorder.
func Record(ctx context.Context, r Recorder, name string, opts *RecordingOptions) (*LiveRecording, error) {
	var rec *LiveRecording

	// Extract the ari client from the recorder
	c := r.GetClient()
	if c == nil {
		return nil, fmt.Errorf("Failed to find *ari.Client in Recorder")
	}

	// Listen for start, stop, and failed events
	startSub := c.Bus.Subscribe("RecordingStarted")
	defer startSub.Cancel()

	finishedSub := c.Bus.Subscribe("RecordingFinished")
	defer finishedSub.Cancel()

	failedSub := c.Bus.Subscribe("RecordingFailed")
	defer failedSub.Cancel()

	// Start recording
	r.Record(name, opts)

	// Wait for the recording to start
	startTimer := time.After(RecordingStartTimeout)
	for rec == nil {
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("Recording canceled.")
		case e := <-startSub.C:
			Logger.Debug("Recording started.")
			r := &(e.(*RecordingStarted).Recording)
			if r.Name == name {
				rec = r
				rec.client = c
			}
		case e := <-finishedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				return nil, fmt.Errorf("Recording stopped before starting")
			}
			if e.GetType() == "RecordingFailed" {
			}
			return nil, fmt.Errorf("Recording stopped before starting.")
		case e := <-failedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				return nil, fmt.Errorf("Recording failed to start.")
			}
		case <-startTimer:
			return nil, fmt.Errorf("Timed out waiting for recording to start.")
		}
	}

	// Make the doneChan on the recording to service Done() requests
	rec.doneChan = make(chan struct{})

	// Listen for the recording to finish
	go func() {
		defer func() {
			rec.Stop()
		}()

		select {
		case <-ctx.Done():
			rec.setStatus(RecordCanceled)
			return
		case <-c.Bus.Once(ctx, "ChannelHangup"):
			rec.setStatus(RecordHangup)
			return
		case <-c.Bus.Once(ctx, "RecordingFinished"):
			rec.setStatus(RecordFinished)
			return
		case <-c.Bus.Once(ctx, "RecordingFailed"):
			rec.setStatus(RecordFailed)
			return
		}
	}()

	return rec, nil
}

//Copy current StoredRecording to a new name (retaining the existing copy)
func (s *StoredRecording) Copy(destination string) (StoredRecording, error) {
	var sRet StoredRecording
	if s.client == nil {
		return sRet, fmt.Errorf("No client found in StoredRecording")
	}
	return s.client.CopyStoredRecording(s.Name, destination)
}

// Done returns a channel which is closed when the LiveRecording
// stops.
func (l *LiveRecording) Done() <-chan struct{} {
	return l.doneChan
}

func (l *LiveRecording) setStatus(status int) {
	l.status = status
}

// Status returns the status of the recording.
func (l *LiveRecording) Status() int {
	return l.status
}

//Stop and store current LiveRecording
func (l *LiveRecording) Stop() error {
	l.mu.Lock()
	if l.doneChan != nil {
		close(l.doneChan)
		l.doneChan = nil
	}
	l.mu.Unlock()
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.StopLiveRecording(l.Name)
}

//Pause current LiveRecording
func (l *LiveRecording) Pause() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.PauseLiveRecording(l.Name)
}

//Mute current LiveRecording
func (l *LiveRecording) Mute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.MuteLiveRecording(l.Name)
}

//Delete current LiveRecording
func (l *LiveRecording) Delete() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.DeleteStoredRecording(l.Name)
}

// Scrap Stops and deletes the current LiveRecording
//TODO: reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (l *LiveRecording) Scrap() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ScrapLiveRecording(l.Name)
}

// Resume unpauses the current LiveRecording
func (l *LiveRecording) Resume() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ResumeLiveRecording(l.Name)
}

// Unmute current LiveRecording
func (l *LiveRecording) Unmute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.UnmuteLiveRecording(l.Name)
}

// CopyStoredRecording copies a stored recording to a new destination
// within the stored recordings tree.
//Equivalent to Post /recordings/stored/{recordingName}/copy
func (c *Client) CopyStoredRecording(recordingName string, destination string) (StoredRecording, error) {
	var m StoredRecording

	//Request structure to copy a stored recording. DestinationRecordingName is required.
	type request struct {
		DestinationRecordingName string `json:"destinationRecordingName"`
	}

	req := request{destination}

	//Make the request
	err := c.Post("/recordings/stored/"+recordingName+"/copy", &m, &req)
	//TODO add individual error handling

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

// StopLiveRecording stops and stores a live recording
//Equivalent to Post /recordings/live/{recordingName}/stop
func (c *Client) StopLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/stop", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//PauseLiveRecording pauses a live recording
//Equivalent to Post /recordings/live/{recordingName}/pause
func (c *Client) PauseLiveRecording(recordingName string) error {

	//Since no request body is required nor return object
	//we just pass two nils.

	err := c.Post("/recordings/live/"+recordingName+"/pause", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//MuteLiveRecording mutes a live recording
//Equivalent to Post /recordings/live/{recordingName}/mute
func (c *Client) MuteLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/mute", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//DeleteStoredRecording deletes a stored recording
//Equivalent to DELETE /recordings/stored/{recordingName}
func (c *Client) DeleteStoredRecording(recordingName string) error {
	err := c.Delete("/recordings/stored/"+recordingName, nil, "")
	return err
}

// ScrapLiveRecording stops a live recording and discard it
//Equivalent to DELETE /recordings/live/{recordingName}
//TODO reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (c *Client) ScrapLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName, nil, "")
	return err
}

// ResumeLiveRecording resumes (unpauses) a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/pause
func (c *Client) ResumeLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/pause", nil, "")
	return err
}

// UnmuteLiveRecording unmutes a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/mute
func (c *Client) UnmuteLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/mute", nil, "")
	return err
}
<commit_msg>Make Record synchronous<commit_after>package ari

import (
	"fmt"
	"sync"
	"time"

	"golang.org/x/net/context"
)

// RecordingStartTimeout is the amount of time to wait for a recording to start
// before declaring the recording to have failed.
var RecordingStartTimeout = 1 * time.Second

// LiveRecording describes a recording which is in progress
type LiveRecording struct {
	Cause       string `json:"cause,omitempty"`            // If failed, the cause of the failure
	DurationSec int    `json:"duration,omitempty"`         // Length of recording in seconds
	Format      string `json:"format"`                     // Format of recording (wav, gsm, etc)
	Name        string `json:"name"`                       // (base) name for the recording
	SilenceSec  int    `json:"silence_duration,omitempty"` // If silence was detected in the recording, the duration in seconds of that silence (requires that maxSilenceSeconds be non-zero)
	State       string `json:"state"`                      // Current state of the recording
	TalkingSec  int    `json:"talking_duration,omitempty"` // Duration of talking, in seconds, that has been detected in the recording (requires that maxSilenceSeconds be non-zero)
	TargetURI   string `json:"target_uri"`                 // URI for the channel or bridge which is being recorded (TODO: figure out format for this)

	client *Client // Reference to the client which created or returned this LiveRecording

	doneChan chan struct{} // channel for indicating the the recording is stopped.
	mu       sync.Mutex

	status int // The status of the live recording
}

// Duration returns the duration of the live recording, if known
func (l *LiveRecording) Duration() (dur time.Duration) {
	if l.DurationSec > 0 {
		dur = time.Duration(l.DurationSec) * time.Second
	}
	return
}

// SilenceDuration returns the duration of the detected silence
// during the recording, if known
func (l *LiveRecording) SilenceDuration() (dur time.Duration) {
	if l.SilenceSec > 0 {
		dur = time.Duration(l.SilenceSec) * time.Second
	}
	return
}

// TalkingDuration returns the duration of the detected talking
// during the recording, if known
func (l *LiveRecording) TalkingDuration() (dur time.Duration) {
	if l.TalkingSec > 0 {
		dur = time.Duration(l.TalkingSec) * time.Second
	}
	return
}

var (
	// ExistsFail indicates that a recording should fail if the
	// given name already exists.
	ExistsFail = "fail"

	// ExistsOverwrite indicates that if a recording exists of
	// the same name, it should be overwritten.
	ExistsOverwrite = "overwrite"

	// ExistsAppend indicates that if a recording exists of the
	// same name, it should be appended to.
	ExistsAppend = "append"
)

var (
	// TerminateNever indicates that a recording should not be
	// ended on any DTMF tone.
	TerminateNever = "none"

	// TerminateAny indicates that a recording should be terminated
	// if any DTMF digit is received
	TerminateAny = "any"

	// TerminateStar indicates that a recording should be terminated
	// if a * DTMF character is received.
	TerminateStar = "*"

	// TerminateHash indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminateHash = "#"

	// TerminatePound indicates that a recording should be terminated
	// if a # DTMF character is received.
	TerminatePound = "#"
)

const (
	// RecordInProgress indicates that a recording is still in progress
	RecordInProgress = iota

	// RecordCanceled indicates that a recording was canceled (by request)
	RecordCanceled

	// RecordFailed indicates that a recording failed
	RecordFailed

	// RecordFinished indicates that a recording finished normally
	RecordFinished

	// RecordHangup indicates that a recording was ended due to hangup
	RecordHangup
)

// RecordingOptions describes the set of options available when making a recording.
type RecordingOptions struct {
	// Format is the file format/encoding to which the recording should be stored.
	// This will usually be one of: slin, ulaw, alaw, wav, gsm.
	// If not specified, this will default to slin.
	Format string

	// MaxDuration is the maximum duration of the recording, after which the recording will
	// automatically stop.  If not set, there is no maximum.
	MaxDuration time.Duration

	// MaxSilence is the maximum duration of detected to be found before terminating the recording.
	MaxSilence time.Duration

	// Exists determines what should happen if the given recording already exists.
	// Valid values are: "fail", "overwrite", or "append".
	// If not specified, it will default to "fail"
	Exists string

	// Beep indicates whether a beep should be played to the recorded
	// party at the beginning of the recording.
	Beep bool

	// Terminate indicates whether the recording should be terminated on
	// receipt of a DTMF digit.
	// valid options are: "none", "any", "*", and "#"
	// If not specified, it will default to "none" (never terminate on DTMF).
	Terminate string
}

// ToRequest converts a set of recording options to a
// record request.
func (o *RecordingOptions) ToRequest(name string) *RecordRequest {
	if o.Format == "" {
		o.Format = "slin"
	}
	return &RecordRequest{
		Name:               name,
		Format:             o.Format,
		MaxDurationSeconds: int(o.MaxDuration.Seconds()),
		MaxSilenceSeconds:  int(o.MaxSilence.Seconds()),
		IfExists:           o.Exists,
		Beep:               o.Beep,
		TerminateOn:        o.Terminate,
	}
}

// StoredRecording describes a past recording which may be played back (via GetStoredRecording)
type StoredRecording struct {
	Format string `json:"format"`
	Name   string `json:"name"`

	client *Client // Reference to the client which created or returned this StoredRecording
}

// A Recorder is anything which can "Record"
type Recorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	GetClient() *Client
}

// A PlayerRecorder is anywhich which can Record and Play
type PlayerRecorder interface {
	Record(string, *RecordingOptions) (*LiveRecording, error)
	Play(string) (string, error)
	GetClient() *Client
}

//ListStoredRecordings lists all completed recordings
//Equivalent to GET /recordings/stored
func (c *Client) ListStoredRecordings() ([]StoredRecording, error) {
	var m []StoredRecording
	err := c.Get("/recordings/stored", &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetStoredRecording returns a stored recording's details
//Equivalent to GET /recordings/stored/{recordingName}
func (c *Client) GetStoredRecording(recordingName string) (StoredRecording, error) {
	var m StoredRecording
	err := c.Get("/recordings/stored/"+recordingName, &m)
	if err != nil {
		return m, err
	}
	return m, nil
}

//GetLiveRecording returns a specific live recording
//Equivalent to GET /recordings/live/{recordingName}
func (c *Client) GetLiveRecording(recordingName string) (*LiveRecording, error) {
	var m LiveRecording
	err := c.Get("/recordings/live/"+recordingName, &m)
	return &m, err
}

// Record starts a recording on the given Recorder.
func Record(ctx context.Context, r Recorder, name string, opts *RecordingOptions) (*LiveRecording, error) {
	// Extract the ari client from the recorder
	c := r.GetClient()
	if c == nil {
		return nil, fmt.Errorf("Failed to find *ari.Client in Recorder")
	}

	// Listen for start, stop, and failed events
	startSub := c.Bus.Subscribe("RecordingStarted")
	defer startSub.Cancel()

	failedSub := c.Bus.Subscribe("RecordingFailed")
	defer failedSub.Cancel()

	finishedSub := c.Bus.Subscribe("RecordingFinished")
	defer finishedSub.Cancel()

	// Start recording
	rec, err := r.Record(name, opts)
	if err != nil {
		return nil, err
	}
	defer rec.Stop()

	// TODO: we have no way to track hangups because we do
	// not have the affiliated channel ID.  We _may_ be able
	// to compare a ChannelHangupRequest event's channel with
	// the LiveRecording's TargetURI, but that will only work
	// for channels.

	// Wait for the recording to start
	startTimer := time.NewTimer(RecordingStartTimeout)
	for {
		select {
		case <-startTimer.C:
			rec.setStatus(RecordFailed)
			return rec, fmt.Errorf("Timed out waiting for recording to start.")
		case <-ctx.Done():
			rec.setStatus(RecordCanceled)
			return rec, fmt.Errorf("Recording canceled.")
		case e := <-startSub.C:
			r := e.(*RecordingStarted).Recording
			if r.Name == name {
				Logger.Debug("Recording started.")
				startTimer.Stop()
				rec = &r
				rec.client = c
			}
		case e := <-failedSub.C:
			r := e.(*RecordingFailed).Recording
			if r.Name == name {
				rec.setStatus(RecordFailed)
				return rec, fmt.Errorf("Recording failed to start.")
			}
		case e := <-finishedSub.C:
			r := e.(*RecordingFinished).Recording
			if r.Name == name {
				Logger.Debug("Recording stopped")
				rec = &r
				rec.client = c
				rec.setStatus(RecordFinished)
				return rec, nil
			}
			return nil, fmt.Errorf("Recording stopped before starting.")
		}
	}
}

//Copy current StoredRecording to a new name (retaining the existing copy)
func (s *StoredRecording) Copy(destination string) (StoredRecording, error) {
	var sRet StoredRecording
	if s.client == nil {
		return sRet, fmt.Errorf("No client found in StoredRecording")
	}
	return s.client.CopyStoredRecording(s.Name, destination)
}

// Done returns a channel which is closed when the LiveRecording
// stops.
func (l *LiveRecording) Done() <-chan struct{} {
	return l.doneChan
}

func (l *LiveRecording) setStatus(status int) {
	l.status = status
}

// Status returns the status of the recording.
func (l *LiveRecording) Status() int {
	return l.status
}

//Stop and store current LiveRecording
func (l *LiveRecording) Stop() error {
	l.mu.Lock()
	if l.doneChan != nil {
		close(l.doneChan)
		l.doneChan = nil
	}
	l.mu.Unlock()
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.StopLiveRecording(l.Name)
}

//Pause current LiveRecording
func (l *LiveRecording) Pause() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.PauseLiveRecording(l.Name)
}

//Mute current LiveRecording
func (l *LiveRecording) Mute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.MuteLiveRecording(l.Name)
}

//Delete current LiveRecording
func (l *LiveRecording) Delete() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.DeleteStoredRecording(l.Name)
}

// Scrap Stops and deletes the current LiveRecording
//TODO: reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (l *LiveRecording) Scrap() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ScrapLiveRecording(l.Name)
}

// Resume unpauses the current LiveRecording
func (l *LiveRecording) Resume() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.ResumeLiveRecording(l.Name)
}

// Unmute current LiveRecording
func (l *LiveRecording) Unmute() error {
	if l.client == nil {
		return fmt.Errorf("No client found in LiveRecording")
	}
	return l.client.UnmuteLiveRecording(l.Name)
}

// CopyStoredRecording copies a stored recording to a new destination
// within the stored recordings tree.
//Equivalent to Post /recordings/stored/{recordingName}/copy
func (c *Client) CopyStoredRecording(recordingName string, destination string) (StoredRecording, error) {
	var m StoredRecording

	//Request structure to copy a stored recording. DestinationRecordingName is required.
	type request struct {
		DestinationRecordingName string `json:"destinationRecordingName"`
	}

	req := request{destination}

	//Make the request
	err := c.Post("/recordings/stored/"+recordingName+"/copy", &m, &req)
	//TODO add individual error handling

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

// StopLiveRecording stops and stores a live recording
//Equivalent to Post /recordings/live/{recordingName}/stop
func (c *Client) StopLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/stop", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//PauseLiveRecording pauses a live recording
//Equivalent to Post /recordings/live/{recordingName}/pause
func (c *Client) PauseLiveRecording(recordingName string) error {

	//Since no request body is required nor return object
	//we just pass two nils.

	err := c.Post("/recordings/live/"+recordingName+"/pause", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//MuteLiveRecording mutes a live recording
//Equivalent to Post /recordings/live/{recordingName}/mute
func (c *Client) MuteLiveRecording(recordingName string) error {
	err := c.Post("/recordings/live/"+recordingName+"/mute", nil, nil)
	if err != nil {
		return err
	}
	return nil
}

//DeleteStoredRecording deletes a stored recording
//Equivalent to DELETE /recordings/stored/{recordingName}
func (c *Client) DeleteStoredRecording(recordingName string) error {
	err := c.Delete("/recordings/stored/"+recordingName, nil, "")
	return err
}

// ScrapLiveRecording stops a live recording and discard it
//Equivalent to DELETE /recordings/live/{recordingName}
//TODO reproduce this error in isolation: does not delete. Cannot delete any recording produced by this.
func (c *Client) ScrapLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName, nil, "")
	return err
}

// ResumeLiveRecording resumes (unpauses) a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/pause
func (c *Client) ResumeLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/pause", nil, "")
	return err
}

// UnmuteLiveRecording unmutes a live recording
//Equivalent to DELETE /recordings/live/{recordingName}/mute
func (c *Client) UnmuteLiveRecording(recordingName string) error {
	err := c.Delete("/recordings/live/"+recordingName+"/mute", nil, "")
	return err
}
<|endoftext|>


<commit_before>// Copyright 2012 Brian "bojo" Jones. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package redistore

import (
	"encoding/base32"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"net/http"
	"strings"
	"time"
)

// Amount of time for cookies/redis keys to expire.
var sessionExpire int = 86400 * 30

// RediStore stores sessions in a redis backend.
type RediStore struct {
	Pool    *redis.Pool
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
}

// NewRediStore returns a new RediStore.
func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) *RediStore {
	return &RediStore{
		// http://godoc.org/github.com/garyburd/redigo/redis#Pool
		Pool: &redis.Pool{
			MaxIdle:     size,
			IdleTimeout: 240 * time.Second,
			Dial: func() (redis.Conn, error) {
				c, err := redis.Dial(network, address)
				if err != nil {
					return nil, err
				}
				if password != "" {
					if _, err := c.Do("AUTH", password); err != nil {
						c.Close()
						return nil, err
					}
				}
				return c, err
			},
			TestOnBorrow: func(c redis.Conn, t time.Time) error {
				_, err := c.Do("PING")
				return err
			},
		},
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: sessionExpire,
		},
	}
}

// Close cleans up the redis connections.
func (s *RediStore) Close() {
	s.Pool.Close()
}

// MaxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new RediStore is 4096. Redis allows for max.
// value sizes of up to 512MB (http://redis.io/topics/data-types)
func (s *RediStore) MaxLength(l int) {
	for _, c := range s.Codecs {
		if codec, ok := c.(*securecookie.SecureCookie); ok {
			codec.MaxLength(l)
		}
	}
}

// Get returns a session for the given name after adding it to the registry.
//
// See gorilla/sessions FilesystemStore.Get().
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
	var err error
	session := sessions.NewSession(s, name)
	session.Options = &(*s.Options)
	session.IsNew = true
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			err = s.load(session)
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	// Marked for deletion.
	if session.Options.MaxAge < 0 {
		if err := s.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
	} else {
		// Build an alphanumeric key for the redis store.
		if session.ID == "" {
			session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
		}
		if err := s.save(session); err != nil {
			return err
		}
		encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
		if err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	}
	return nil
}

// Delete removes the session from redis, and sets the cookie to expire.
//
// WARNING: This method should be considered deprecated since it is not exposed via the gorilla/sessions interface.
// Set session.Options.MaxAge = -1 and call Save instead. - July 18th, 2013
func (s *RediStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	// Set cookie to expire.
	options := *session.Options
	options.MaxAge = -1
	http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
	// Clear session values.
	for k := range session.Values {
		delete(session.Values, k)
	}
	return nil
}

// save stores the session in redis.
func (s *RediStore) save(session *sessions.Session) error {
	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...)
	if err != nil {
		return err
	}
	conn := s.Pool.Get()
	defer conn.Close()
	if err = conn.Err(); err != nil {
		return err
	}
	_, err = conn.Do("SET", "session_"+session.ID, encoded, "EX", session.Options.MaxAge)
	return err
}

// load reads the session from redis.
func (s *RediStore) load(session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if err := conn.Err(); err != nil {
		return err
	}
	data, err := conn.Do("GET", "session_"+session.ID)
	if err != nil {
		return err
	}
	if data == nil {
		return nil // no data was associated with this key
	}
	str, err := redis.String(data, err)
	if err != nil {
		return err
	}
	if err = securecookie.DecodeMulti(session.Name(), str, &session.Values, s.Codecs...); err != nil {
		return err
	}
	return nil
}

// delete removes keys from redis if MaxAge<0
func (s *RediStore) delete(session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	return nil
}
<commit_msg>fixes #7 - see long desc for why<commit_after>// Copyright 2012 Brian "bojo" Jones. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package redistore

import (
	"encoding/base32"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"net/http"
	"strings"
	"time"
)

// Amount of time for cookies/redis keys to expire.
var sessionExpire int = 86400 * 30

// RediStore stores sessions in a redis backend.
type RediStore struct {
	Pool    *redis.Pool
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
}

// NewRediStore returns a new RediStore.
func NewRediStore(size int, network, address, password string, keyPairs ...[]byte) *RediStore {
	return &RediStore{
		// http://godoc.org/github.com/garyburd/redigo/redis#Pool
		Pool: &redis.Pool{
			MaxIdle:     size,
			IdleTimeout: 240 * time.Second,
			Dial: func() (redis.Conn, error) {
				c, err := redis.Dial(network, address)
				if err != nil {
					return nil, err
				}
				if password != "" {
					if _, err := c.Do("AUTH", password); err != nil {
						c.Close()
						return nil, err
					}
				}
				return c, err
			},
			TestOnBorrow: func(c redis.Conn, t time.Time) error {
				_, err := c.Do("PING")
				return err
			},
		},
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: sessionExpire,
		},
	}
}

// Close cleans up the redis connections.
func (s *RediStore) Close() {
	s.Pool.Close()
}

// MaxLength restricts the maximum length of new sessions to l.
// If l is 0 there is no limit to the size of a session, use with caution.
// The default for a new RediStore is 4096. Redis allows for max.
// value sizes of up to 512MB (http://redis.io/topics/data-types)
func (s *RediStore) MaxLength(l int) {
	for _, c := range s.Codecs {
		if codec, ok := c.(*securecookie.SecureCookie); ok {
			codec.MaxLength(l)
		}
	}
}

// Get returns a session for the given name after adding it to the registry.
//
// See gorilla/sessions FilesystemStore.Get().
func (s *RediStore) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
	var err error
	session := sessions.NewSession(s, name)
	session.Options = &(*s.Options)
	session.IsNew = true
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			err = s.load(session)
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	// Marked for deletion.
	if session.Options.MaxAge < 0 {
		if err := s.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
	} else {
		// Build an alphanumeric key for the redis store.
		if session.ID == "" {
			session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
		}
		if err := s.save(session); err != nil {
			return err
		}
		encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
		if err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	}
	return nil
}

// Delete removes the session from redis, and sets the cookie to expire.
//
// WARNING: This method should be considered deprecated since it is not exposed via the gorilla/sessions interface.
// Set session.Options.MaxAge = -1 and call Save instead. - July 18th, 2013
func (s *RediStore) Delete(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	// Set cookie to expire.
	options := *session.Options
	options.MaxAge = -1
	http.SetCookie(w, sessions.NewCookie(session.Name(), "", &options))
	// Clear session values.
	for k := range session.Values {
		delete(session.Values, k)
	}
	return nil
}

// save stores the session in redis.
func (s *RediStore) save(session *sessions.Session) error {
	encoded, err := securecookie.EncodeMulti(session.Name(), session.Values, s.Codecs...)
	if err != nil {
		return err
	}
	conn := s.Pool.Get()
	defer conn.Close()
	if err = conn.Err(); err != nil {
		return err
	}
	_, err = conn.Do("SETEX", "session_"+session.ID, session.Options.MaxAge, encoded)
	return err
}

// load reads the session from redis.
func (s *RediStore) load(session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if err := conn.Err(); err != nil {
		return err
	}
	data, err := conn.Do("GET", "session_"+session.ID)
	if err != nil {
		return err
	}
	if data == nil {
		return nil // no data was associated with this key
	}
	str, err := redis.String(data, err)
	if err != nil {
		return err
	}
	if err = securecookie.DecodeMulti(session.Name(), str, &session.Values, s.Codecs...); err != nil {
		return err
	}
	return nil
}

// delete removes keys from redis if MaxAge<0
func (s *RediStore) delete(session *sessions.Session) error {
	conn := s.Pool.Get()
	defer conn.Close()
	if _, err := conn.Do("DEL", "session_"+session.ID); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before><commit_msg>Add a Scanner usage example<commit_after><|endoftext|>


<commit_before>// Package stack implements a stack
package stack

// stack is the internal representation of the data structure
type stack struct {
	storage []interface{}
	i       int
}

// New returns an initialized stack of a given size
func New(size int) *stack {
	s := new(stack)
	s.storage = make([]interface{}, size)
	s.i = -1

	return s
}

// Push adds a new element to the top of the stack
// O(1)
func (s *stack) Push(v interface{}) {
	// dynamically increase the size of storage as needed
	if s.i+1 == cap(s.storage) {
		ns := make([]interface{}, s.i*2)
		copy(ns, s.storage)
		s.storage = ns
	}

	s.i++
	s.storage[s.i] = v
}

// Pop removes the top element from the stack
// O(1).
func (s *stack) Pop() interface{} {
	if s.i < 0 {
		return nil
	}

	v := s.storage[s.i]
	s.i--

	return v
}

// Peek returns the top element from the stack without removing it
// O(1)
func (s *stack) Peek() interface{} {
	if s.i < 0 {
		return nil
	}

	return s.storage[s.i]
}

// IsEmpty returns a value indicating whether the stack has any elements
// O(1)
func (s *stack) IsEmpty() bool {
	return s.Len() == 0
}

// Len returns the number of elements in the stack
// O(1)
func (s *stack) Len() int {
	return s.i + 1
}
<commit_msg>properly remove element from set<commit_after>// Package stack implements a stack
package stack

// stack is the internal representation of the data structure
type stack struct {
	storage []interface{}
	i       int
}

// New returns an initialized stack of a given size
func New(size int) *stack {
	s := new(stack)
	s.storage = make([]interface{}, size)
	s.i = -1

	return s
}

// Push adds a new element to the top of the stack
// O(1)
func (s *stack) Push(v interface{}) {
	// dynamically increase the size of storage as needed
	if s.i+1 == cap(s.storage) {
		ns := make([]interface{}, s.i*2)
		copy(ns, s.storage)
		s.storage = ns
	}

	s.i++
	s.storage[s.i] = v
}

// Pop removes the top element from the stack
// O(1).
func (s *stack) Pop() interface{} {
	if s.i < 0 {
		return nil
	}

	v := s.storage[s.i]
	s.storage[s.i] = nil
	s.i--

	return v
}

// Peek returns the top element from the stack without removing it
// O(1)
func (s *stack) Peek() interface{} {
	if s.i < 0 {
		return nil
	}

	return s.storage[s.i]
}

// IsEmpty returns a value indicating whether the stack has any elements
// O(1)
func (s *stack) IsEmpty() bool {
	return s.Len() == 0
}

// Len returns the number of elements in the stack
// O(1)
func (s *stack) Len() int {
	return s.i + 1
}
<|endoftext|>


<commit_before>package device

import (
	"bytes"
	"context"
	"fmt"
	"github.com/Comcast/webpa-common/httperror"
	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/wrp"
	"io"
	"net/http"
	"sync"
	"time"
)

const DefaultMessageTimeout time.Duration = time.Minute * 2

// MessageHandler is a configurable http.Handler which handles inbound WRP traffic
// to be sent to devices.
type MessageHandler struct {
	// Logger is the sink for logging output.  If not set, logging will be sent to logging.DefaultLogger().
	Logger logging.Logger

	// Decoders is the pool of wrp.Decoder objects used to decode http.Request bodies
	// sent to this handler.  This field is required.
	Decoders *wrp.DecoderPool

	// Encoders is the pool of wrp.Encoder objects used to encode wrp messages sent
	// as HTTP responses.  This field is required.
	Encoders *wrp.EncoderPool

	// Router is the device message Router to use.  This field is required.
	Router Router

	// Timeout is the optional timeout for all operations through this handler.
	// If this field is unset or is nonpositive, DefaultMessageTimeout is used instead.
	Timeout time.Duration
}

func (mh *MessageHandler) logger() logging.Logger {
	if mh.Logger != nil {
		return mh.Logger
	}

	return logging.DefaultLogger()
}

// createContext creates the Context object for routing operations.
// This method will never return nils.  There will always be a timeout on the
// returned context, which means there will always be a cancel function too.
func (mh *MessageHandler) createContext(httpRequest *http.Request) (context.Context, context.CancelFunc) {
	timeout := mh.Timeout
	if timeout < 1 {
		timeout = DefaultMessageTimeout
	}

	return context.WithTimeout(httpRequest.Context(), mh.Timeout)
}

// decodeRequest transforms an HTTP request into a device request.
func (mh *MessageHandler) decodeRequest(ctx context.Context, httpRequest *http.Request) (deviceRequest *Request, err error) {
	deviceRequest, err = DecodeRequest(httpRequest.Body, mh.Decoders)
	if err == nil {
		deviceRequest = deviceRequest.WithContext(ctx)
	}

	return
}

func (mh *MessageHandler) ServeHTTP(httpResponse http.ResponseWriter, httpRequest *http.Request) {
	ctx, cancel := mh.createContext(httpRequest)
	defer cancel()

	deviceRequest, err := mh.decodeRequest(ctx, httpRequest)
	if err != nil {
		httperror.Formatf(
			httpResponse,
			http.StatusBadRequest,
			"Could not decode WRP message: %s",
			err,
		)

		return
	}

	// deviceRequest carries the context through the routing infrastructure
	if deviceResponse, err := mh.Router.Route(deviceRequest); err != nil {
		code := http.StatusInternalServerError
		switch err {
		case ErrorInvalidDeviceName:
			code = http.StatusBadRequest
		case ErrorDeviceNotFound:
			code = http.StatusNotFound
		case ErrorNonUniqueID:
			code = http.StatusBadRequest
		}

		httperror.Formatf(
			httpResponse,
			code,
			"Could not process device request: %s",
			err,
		)
	} else if deviceResponse != nil {
		if err := EncodeResponse(httpResponse, deviceResponse, mh.Encoders); err != nil {
			mh.logger().Error("Error while writing transaction response: %s", err)
		}
	}

	// if deviceReponse == nil, that just means the request was not something that represented
	// the start of a transaction.  For example, events do not carry a transaction key because
	// they do not expect responses.
}

type ConnectHandler struct {
	Logger         logging.Logger
	Connector      Connector
	ResponseHeader http.Header
}

func (ch *ConnectHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	logger := ch.Logger
	if logger == nil {
		logger = logging.DefaultLogger()
	}

	device, err := ch.Connector.Connect(response, request, ch.ResponseHeader)
	if err != nil {
		logger.Error("Failed to connect device: %s", err)
	} else {
		logger.Debug("Connected device: %s", device.ID())
	}
}

// ListHandler is an http.Handler that responds with lists of devices associated with a Registry.
type ListHandler struct {
	Logger logging.Logger

	// Registry is the required instance containing registered devices
	Registry Registry

	// CachePeriod is the length of time that the internally cached JSON is considered valid.
	// If nonpositive, this handler does no caching.
	CachePeriod time.Duration

	lock        sync.RWMutex
	cacheExpiry time.Time
	cachedJSON  []byte
}

func (lh *ListHandler) logger() logging.Logger {
	if lh.Logger != nil {
		return lh.Logger
	}

	return logging.DefaultLogger()
}

func (lh *ListHandler) generateList(output io.Writer) (err error) {
	_, err = fmt.Fprint(output, `{"devices":[`)
	if err != nil {
		return
	}

	comma := ""
	lh.Registry.VisitAll(func(device Interface) {
		if err == nil {
			_, err = fmt.Fprint(output, comma, device.String())
			comma = ","
		}
	})

	if err == nil {
		_, err = fmt.Fprint(output, `]}`)
	}

	return
}

func (lh *ListHandler) tryCache() (json []byte, expired bool) {
	lh.lock.RLock()
	defer lh.lock.RUnlock()

	expired = lh.cacheExpiry.Before(time.Now())
	json = lh.cachedJSON

	return
}

func (lh *ListHandler) updateCache() (json []byte, err error) {
	lh.lock.Lock()
	defer lh.lock.Unlock()

	var output bytes.Buffer
	err = lh.generateList(&output)
	if err != nil {
		return
	}

	lh.cacheExpiry = time.Now().Add(lh.CachePeriod)
	lh.cachedJSON = output.Bytes()
	json = lh.cachedJSON
	return
}

func (lh *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	if lh.CachePeriod < 1 {
		// uncached
		response.Header().Set("Content-Type", "application/json")
		if err := lh.generateList(response); err != nil {
			lh.logger().Error("Unable to output device list: %s", err)
		}

		return
	}

	json, expired := lh.tryCache()
	if expired {
		var err error
		if json, err = lh.updateCache(); err != nil {
			httperror.Formatf(
				response,
				http.StatusInternalServerError,
				"Could not update cached device list: %s",
				err,
			)

			return
		}
	}

	response.Header().Set("Content-Type", "application/json")
	if _, err := response.Write(json); err != nil {
		lh.logger().Error("Unable to output device list: %s", err)
	}
}
<commit_msg>Doublecheck the cache expiry during updateCache<commit_after>package device

import (
	"bytes"
	"context"
	"fmt"
	"github.com/Comcast/webpa-common/httperror"
	"github.com/Comcast/webpa-common/logging"
	"github.com/Comcast/webpa-common/wrp"
	"io"
	"net/http"
	"sync"
	"time"
)

const DefaultMessageTimeout time.Duration = time.Minute * 2

// MessageHandler is a configurable http.Handler which handles inbound WRP traffic
// to be sent to devices.
type MessageHandler struct {
	// Logger is the sink for logging output.  If not set, logging will be sent to logging.DefaultLogger().
	Logger logging.Logger

	// Decoders is the pool of wrp.Decoder objects used to decode http.Request bodies
	// sent to this handler.  This field is required.
	Decoders *wrp.DecoderPool

	// Encoders is the pool of wrp.Encoder objects used to encode wrp messages sent
	// as HTTP responses.  This field is required.
	Encoders *wrp.EncoderPool

	// Router is the device message Router to use.  This field is required.
	Router Router

	// Timeout is the optional timeout for all operations through this handler.
	// If this field is unset or is nonpositive, DefaultMessageTimeout is used instead.
	Timeout time.Duration
}

func (mh *MessageHandler) logger() logging.Logger {
	if mh.Logger != nil {
		return mh.Logger
	}

	return logging.DefaultLogger()
}

// createContext creates the Context object for routing operations.
// This method will never return nils.  There will always be a timeout on the
// returned context, which means there will always be a cancel function too.
func (mh *MessageHandler) createContext(httpRequest *http.Request) (context.Context, context.CancelFunc) {
	timeout := mh.Timeout
	if timeout < 1 {
		timeout = DefaultMessageTimeout
	}

	return context.WithTimeout(httpRequest.Context(), mh.Timeout)
}

// decodeRequest transforms an HTTP request into a device request.
func (mh *MessageHandler) decodeRequest(ctx context.Context, httpRequest *http.Request) (deviceRequest *Request, err error) {
	deviceRequest, err = DecodeRequest(httpRequest.Body, mh.Decoders)
	if err == nil {
		deviceRequest = deviceRequest.WithContext(ctx)
	}

	return
}

func (mh *MessageHandler) ServeHTTP(httpResponse http.ResponseWriter, httpRequest *http.Request) {
	ctx, cancel := mh.createContext(httpRequest)
	defer cancel()

	deviceRequest, err := mh.decodeRequest(ctx, httpRequest)
	if err != nil {
		httperror.Formatf(
			httpResponse,
			http.StatusBadRequest,
			"Could not decode WRP message: %s",
			err,
		)

		return
	}

	// deviceRequest carries the context through the routing infrastructure
	if deviceResponse, err := mh.Router.Route(deviceRequest); err != nil {
		code := http.StatusInternalServerError
		switch err {
		case ErrorInvalidDeviceName:
			code = http.StatusBadRequest
		case ErrorDeviceNotFound:
			code = http.StatusNotFound
		case ErrorNonUniqueID:
			code = http.StatusBadRequest
		}

		httperror.Formatf(
			httpResponse,
			code,
			"Could not process device request: %s",
			err,
		)
	} else if deviceResponse != nil {
		if err := EncodeResponse(httpResponse, deviceResponse, mh.Encoders); err != nil {
			mh.logger().Error("Error while writing transaction response: %s", err)
		}
	}

	// if deviceReponse == nil, that just means the request was not something that represented
	// the start of a transaction.  For example, events do not carry a transaction key because
	// they do not expect responses.
}

type ConnectHandler struct {
	Logger         logging.Logger
	Connector      Connector
	ResponseHeader http.Header
}

func (ch *ConnectHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	logger := ch.Logger
	if logger == nil {
		logger = logging.DefaultLogger()
	}

	device, err := ch.Connector.Connect(response, request, ch.ResponseHeader)
	if err != nil {
		logger.Error("Failed to connect device: %s", err)
	} else {
		logger.Debug("Connected device: %s", device.ID())
	}
}

// ListHandler is an http.Handler that responds with lists of devices associated with a Registry.
type ListHandler struct {
	Logger logging.Logger

	// Registry is the required instance containing registered devices
	Registry Registry

	// CachePeriod is the length of time that the internally cached JSON is considered valid.
	// If nonpositive, this handler does no caching.
	CachePeriod time.Duration

	lock        sync.RWMutex
	cacheExpiry time.Time
	cachedJSON  []byte
}

func (lh *ListHandler) logger() logging.Logger {
	if lh.Logger != nil {
		return lh.Logger
	}

	return logging.DefaultLogger()
}

func (lh *ListHandler) generateList(output io.Writer) (err error) {
	_, err = fmt.Fprint(output, `{"devices":[`)
	if err != nil {
		return
	}

	comma := ""
	lh.Registry.VisitAll(func(device Interface) {
		if err == nil {
			_, err = fmt.Fprint(output, comma, device.String())
			comma = ","
		}
	})

	if err == nil {
		_, err = fmt.Fprint(output, `]}`)
	}

	return
}

func (lh *ListHandler) tryCache() (json []byte, expired bool) {
	lh.lock.RLock()
	defer lh.lock.RUnlock()

	expired = lh.cacheExpiry.Before(time.Now())
	json = lh.cachedJSON

	return
}

func (lh *ListHandler) updateCache() (json []byte, err error) {
	lh.lock.Lock()
	defer lh.lock.Unlock()

	if lh.cacheExpiry.Before(time.Now()) {
		json = lh.cachedJSON
		return
	}

	var output bytes.Buffer
	err = lh.generateList(&output)
	if err != nil {
		return
	}

	lh.cacheExpiry = time.Now().Add(lh.CachePeriod)
	lh.cachedJSON = output.Bytes()
	json = lh.cachedJSON
	return
}

func (lh *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	if lh.CachePeriod < 1 {
		// uncached
		response.Header().Set("Content-Type", "application/json")
		if err := lh.generateList(response); err != nil {
			lh.logger().Error("Unable to output device list: %s", err)
		}

		return
	}

	json, expired := lh.tryCache()
	if expired {
		var err error
		if json, err = lh.updateCache(); err != nil {
			httperror.Formatf(
				response,
				http.StatusInternalServerError,
				"Could not update cached device list: %s",
				err,
			)

			return
		}
	}

	response.Header().Set("Content-Type", "application/json")
	if _, err := response.Write(json); err != nil {
		lh.logger().Error("Unable to output device list: %s", err)
	}
}
<|endoftext|>


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

package pages

const containersHtmlTemplate = `

    <title>cAdvisor - {{.DisplayName}}</title>

    <link rel="stylesheet" href="/static/bootstrap-3.1.1.min.css">

    <link rel="stylesheet" href="/static/bootstrap-theme-3.1.1.min.css">

    <link rel="stylesheet" href="/static/containers.css">

{{.DisplayName}}

	  {{range $parentContainer := .ParentContainers}}

{{$parentContainer.Text}}

	  {{end}}

      {{if .IsRoot}}

Docker Containers

      {{end}}
      {{if .Subcontainers}}

Subcontainers

	  {{range $subcontainer := .Subcontainers}}
	  {{$subcontainer.Text}}
	  {{end}}

      {{end}}
      {{if .ResourcesAvailable}}

Isolation

	{{if .CpuAvailable}}

CPU

          {{if .Spec.Cpu.Limit}}

Shares {{printShares .Spec.Cpu.Limit}} shares

          {{end}}
          {{if .Spec.Cpu.MaxLimit}}

Max Limit {{printCores .Spec.Cpu.MaxLimit}} cores

          {{end}}
          {{if .Spec.Cpu.Mask}}

Allowed Cores {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}

          {{end}}

	{{end}}
	{{if .MemoryAvailable}}

Memory

          {{if .Spec.Memory.Reservation}}

Reservation {{printSize .Spec.Memory.Reservation}} {{printUnit .Spec.Memory.Reservation}}

          {{end}}
          {{if .Spec.Memory.Limit}}

Limit {{printSize .Spec.Memory.Limit}} {{printUnit .Spec.Memory.Limit}}

          {{end}}
          {{if .Spec.Memory.SwapLimit}}

Swap Limit {{printSize .Spec.Memory.SwapLimit}} {{printUnit .Spec.Memory.SwapLimit}}

          {{end}}

	{{end}}

Usage

Overview

	{{if .CpuAvailable}}

CPU

Total Usage

Usage per Core

Usage Breakdown

	{{end}}
	{{if .MemoryAvailable}}

Memory

Total Usage

Usage Breakdown

                    Hot Memory

                    Cold Memory

		{{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%)

	{{end}}
	{{if .FsAvailable}}

Filesystem

            {{with getFsStats .Stats}}
	    {{range $i, $e := .}}

Partition: {{$e.Device}}

	      {{printSize $e.Limit}} {{printUnit $e.Limit}} ({{getFsUsagePercent $e.Limit $e.Usage}}%)

	    {{end}}
	    {{end}}

	{{end}}
	{{if .NetworkAvailable}}

Network

Throughput

Errors

        {{end}}

      {{end}}

`
<commit_msg>Match Filesystem listing with Filesystem Gauge name. Both are now FS #n.<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pages

const containersHtmlTemplate = `

    <title>cAdvisor - {{.DisplayName}}</title>

    <link rel="stylesheet" href="/static/bootstrap-3.1.1.min.css">

    <link rel="stylesheet" href="/static/bootstrap-theme-3.1.1.min.css">

    <link rel="stylesheet" href="/static/containers.css">

{{.DisplayName}}

	  {{range $parentContainer := .ParentContainers}}

{{$parentContainer.Text}}

	  {{end}}

      {{if .IsRoot}}

Docker Containers

      {{end}}
      {{if .Subcontainers}}

Subcontainers

	  {{range $subcontainer := .Subcontainers}}
	  {{$subcontainer.Text}}
	  {{end}}

      {{end}}
      {{if .ResourcesAvailable}}

Isolation

	{{if .CpuAvailable}}

CPU

          {{if .Spec.Cpu.Limit}}

Shares {{printShares .Spec.Cpu.Limit}} shares

          {{end}}
          {{if .Spec.Cpu.MaxLimit}}

Max Limit {{printCores .Spec.Cpu.MaxLimit}} cores

          {{end}}
          {{if .Spec.Cpu.Mask}}

Allowed Cores {{printMask .Spec.Cpu.Mask .MachineInfo.NumCores}}

          {{end}}

	{{end}}
	{{if .MemoryAvailable}}

Memory

          {{if .Spec.Memory.Reservation}}

Reservation {{printSize .Spec.Memory.Reservation}} {{printUnit .Spec.Memory.Reservation}}

          {{end}}
          {{if .Spec.Memory.Limit}}

Limit {{printSize .Spec.Memory.Limit}} {{printUnit .Spec.Memory.Limit}}

          {{end}}
          {{if .Spec.Memory.SwapLimit}}

Swap Limit {{printSize .Spec.Memory.SwapLimit}} {{printUnit .Spec.Memory.SwapLimit}}

          {{end}}

	{{end}}

Usage

Overview

	{{if .CpuAvailable}}

CPU

Total Usage

Usage per Core

Usage Breakdown

	{{end}}
	{{if .MemoryAvailable}}

Memory

Total Usage

Usage Breakdown

                    Hot Memory

                    Cold Memory

		{{ getMemoryUsage .Stats }} MB ({{ getMemoryUsagePercent .Spec .Stats .MachineInfo}}%)

	{{end}}
	{{if .FsAvailable}}

Filesystem

            {{with getFsStats .Stats}}
	    {{range $i, $e := .}}

FS #{{$i}}: {{$e.Device}}

	      {{printSize $e.Limit}} {{printUnit $e.Limit}} ({{getFsUsagePercent $e.Limit $e.Usage}}%)

	    {{end}}
	    {{end}}

	{{end}}
	{{if .NetworkAvailable}}

Network

Throughput

Errors

        {{end}}

      {{end}}

`
<|endoftext|>


<commit_before>package inigo_test

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"time"

	"github.com/cloudfoundry-incubator/inigo/loggredile"
	"github.com/fraenkel/candiedyaml"
	"github.com/vito/cmdtest"

	"github.com/cloudfoundry-incubator/inigo/archiver"
	"github.com/cloudfoundry-incubator/inigo/inigo_server"
	"github.com/cloudfoundry-incubator/inigo/stager_runner"
	"github.com/cloudfoundry-incubator/runtime-schema/models/factories"
	"github.com/cloudfoundry/yagnats"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Stager", func() {
	var otherStagerRunner *stager_runner.StagerRunner

	BeforeEach(func() {
		fileServerRunner.Start()
		otherStagerRunner = stager_runner.New(
			stagerPath,
			etcdRunner.NodeURLS(),
			[]string{fmt.Sprintf("127.0.0.1:%d", natsPort)},
		)
	})

	Context("when unable to find an appropriate compiler", func() {
		BeforeEach(func() {
			executorRunner.Start()
			stagerRunner.Start()
		})

		It("returns an error", func() {
			receivedMessages := make(chan *yagnats.Message)
			natsRunner.MessageBus.Subscribe("compiler-stagers-test", func(message *yagnats.Message) {
				receivedMessages <- message
			})

			natsRunner.MessageBus.PublishWithReplyTo(
				"diego.staging.start",
				"compiler-stagers-test",
				[]byte(`{
					"app_id": "some-app-guid",
					"task_id": "some-task-id",
					"app_bits_download_uri": "some-download-uri",
					"build_artifacts_cache_download_uri": "artifacts-download-uri",
					"build_artifacts_cache_upload_uri": "artifacts-upload-uri",
					"stack": "no-compiler"
				}`),
			)

			var receivedMessage *yagnats.Message
			Eventually(receivedMessages, SHORT_TIMEOUT).Should(Receive(&receivedMessage))
			Ω(receivedMessage.Payload).Should(ContainSubstring("no compiler defined for requested stack"))
			Consistently(receivedMessages, SHORT_TIMEOUT).ShouldNot(Receive())
		})
	})

	Describe("Staging", func() {
		var outputGuid string
		var stagingMessage []byte
		var buildpackToUse string

		BeforeEach(func() {
			executorRunner.Start()

			buildpackToUse = "admin_buildpack.zip"
			outputGuid = factories.GenerateGuid()

			fileServerRunner.ServeFile("smelter.zip", smelterZipPath)

			//make and upload an app
			var appFiles = []archiver.ArchiveFile{
				{"my-app", "scooby-doo"},
			}

			archiver.CreateZipArchive("/tmp/app.zip", appFiles)
			inigo_server.UploadFile("app.zip", "/tmp/app.zip")

			//make and upload a buildpack
			var adminBuildpackFiles = []archiver.ArchiveFile{
				{"bin/detect", `#!/bin/bash
echo My Buildpack
				`},
				{"bin/compile", `#!/bin/bash
echo $1 $2
echo COMPILING BUILDPACK
echo $SOME_STAGING_ENV
touch $1/compiled
touch $2/inserted-into-artifacts-cache
				`},
				{"bin/release", `#!/bin/bash
cat <<EOF
---
default_process_types:
  web: start-command
EOF
				`},
			}
			archiver.CreateZipArchive("/tmp/admin_buildpack.zip", adminBuildpackFiles)
			inigo_server.UploadFile("admin_buildpack.zip", "/tmp/admin_buildpack.zip")

			var bustedAdminBuildpackFiles = []archiver.ArchiveFile{
				{"bin/detect", `#!/bin/bash]
				exit 1
				`},
				{"bin/compile", `#!/bin/bash`},
				{"bin/release", `#!/bin/bash`},
			}

			archiver.CreateZipArchive("/tmp/busted_admin_buildpack.zip", bustedAdminBuildpackFiles)
			inigo_server.UploadFile("busted_admin_buildpack.zip", "/tmp/busted_admin_buildpack.zip")

			var buildArtifacts = []archiver.ArchiveFile{
				{"pulled-down-from-artifacts-cache", "some-contents-for-pulled-down-file"},
			}

			archiver.CreateTarGZArchive("/tmp/artifacts.tgz", buildArtifacts)
			inigo_server.UploadFile("artifacts.tgz", "/tmp/artifacts.tgz")
		})

		JustBeforeEach(func() {
			stagingMessage = []byte(
				fmt.Sprintf(
					`{
						"app_id": "some-app-guid",
						"task_id": "some-task-id",
						"memory_mb": 128,
						"disk_mb": 128,
						"file_descriptors": 1024,
						"stack": "default",
						"app_bits_download_uri": "%s",
						"build_artifacts_cache_download_uri": "%s",
						"build_artifacts_cache_upload_uri": "%s",
						"buildpacks" : [{ "key": "test-buildpack", "url": "%s" }],
						"environment": [["SOME_STAGING_ENV", "%s"]]
					}`,
					inigo_server.DownloadUrl("app.zip"),
					inigo_server.DownloadUrl("artifacts.tgz"),
					inigo_server.UploadUrl("uploaded-artifacts.tgz"),
					inigo_server.DownloadUrl(buildpackToUse),
					outputGuid,
				),
			)
		})

		Context("with one stager running", func() {
			BeforeEach(func() {
				stagerRunner.Start("--compilers", `{"default":"smelter.zip"}`)
			})

			It("runs the compiler on the executor with the correct environment variables, bits and log tag, and responds with the detected buildpack", func() {
				//listen for NATS response
				payloads := make(chan []byte)

				natsRunner.MessageBus.Subscribe("stager-test", func(msg *yagnats.Message) {
					payloads <- msg.Payload
				})

				//stream logs
				messages, stop := loggredile.StreamMessages(
					loggregatorRunner.Config.OutgoingPort,
					"/tail/?app=some-app-guid",
				)
				defer close(stop)

				logOutput := ""
				go func() {
					for message := range messages {
						Ω(message.GetSourceName()).To(Equal("STG"))
						logOutput += string(message.GetMessage()) + "\n"
					}
				}()

				//publish the staging message
				err := natsRunner.MessageBus.PublishWithReplyTo(
					"diego.staging.start",
					"stager-test",
					stagingMessage,
				)
				Ω(err).ShouldNot(HaveOccurred())

				//wait for staging to complete
				var payload []byte
				Eventually(payloads, LONG_TIMEOUT).Should(Receive(&payload))

				//Assert on the staging output (detected buildpack)
				Ω(string(payload)).Should(Equal(`{"detected_buildpack":"My Buildpack"}`))

				//Asser the user saw reasonable output
				Eventually(func() string {
					return logOutput
				}).Should(ContainSubstring("COMPILING BUILDPACK"))
				Ω(logOutput).Should(ContainSubstring(outputGuid))

				// Assert that the build artifacts cache was downloaded

				// Assert that the build artifacts cache was uploaded again
				artifactsCache, err := gzip.NewReader(inigo_server.DownloadFile("uploaded-artifacts.tgz"))
				Ω(err).ShouldNot(HaveOccurred())

				untarredBuildArtifactsData := tar.NewReader(artifactsCache)
				buildArtifactContents := map[string][]byte{}
				for {
					hdr, err := untarredBuildArtifactsData.Next()
					if err == io.EOF {
						break
					}

					Ω(err).ShouldNot(HaveOccurred())

					content, err := ioutil.ReadAll(untarredBuildArtifactsData)
					Ω(err).ShouldNot(HaveOccurred())

					buildArtifactContents[hdr.Name] = content
				}

				Ω(buildArtifactContents).Should(HaveKey("pulled-down-from-artifacts-cache"))
				Ω(buildArtifactContents).Should(HaveKey("inserted-into-artifacts-cache"))

				//Fetch the compiled droplet from the fakeCC
				dropletData, ok := fakeCC.UploadedDroplets["some-app-guid"]
				Ω(ok).Should(BeTrue())
				Ω(dropletData).ShouldNot(BeEmpty())

				//Unzip the droplet
				ungzippedDropletData, err := gzip.NewReader(bytes.NewReader(dropletData))
				Ω(err).ShouldNot(HaveOccurred())

				//Untar the droplet
				untarredDropletData := tar.NewReader(ungzippedDropletData)
				dropletContents := map[string][]byte{}
				for {
					hdr, err := untarredDropletData.Next()
					if err == io.EOF {
						break
					}
					Ω(err).ShouldNot(HaveOccurred())

					content, err := ioutil.ReadAll(untarredDropletData)
					Ω(err).ShouldNot(HaveOccurred())

					dropletContents[hdr.Name] = content
				}

				//Assert the droplet has the right files in it
				Ω(dropletContents).Should(HaveKey("staging_info.yml"))
				Ω(dropletContents).Should(HaveKey("logs/"))
				Ω(dropletContents).Should(HaveKey("tmp/"))
				Ω(dropletContents).Should(HaveKey("app/"))
				Ω(dropletContents).Should(HaveKey("app/my-app"))
				Ω(dropletContents).Should(HaveKey("app/compiled"))

				//Assert the files contain the right content
				Ω(string(dropletContents["app/my-app"])).Should(Equal("scooby-doo"))

				//In particular, staging_info.yml should have the correct detected_buildpack and start_command
				yamlDecoder := candiedyaml.NewDecoder(bytes.NewReader(dropletContents["staging_info.yml"]))
				stagingInfo := map[string]string{}
				err = yamlDecoder.Decode(&stagingInfo)
				Ω(err).ShouldNot(HaveOccurred())

				Ω(stagingInfo["detected_buildpack"]).Should(Equal("My Buildpack"))
				Ω(stagingInfo["start_command"]).Should(Equal("start-command"))

				//Assert nothing else crept into the droplet
				Ω(dropletContents).Should(HaveLen(6))
			})

			Context("when compilation fails", func() {
				BeforeEach(func() {
					buildpackToUse = "busted_admin_buildpack.zip"
				})

				It("responds with the error, and no detected buildpack present", func() {
					payloads := make(chan []byte)

					natsRunner.MessageBus.Subscribe("stager-test", func(msg *yagnats.Message) {
						payloads <- msg.Payload
					})

					messages, stop := loggredile.StreamMessages(
						loggregatorRunner.Config.OutgoingPort,
						"/tail/?app=some-app-guid",
					)
					defer close(stop)

					logIn, logOut := io.Pipe()
					go func() {
						for message := range messages {
							Ω(message.GetSourceName()).To(Equal("STG"))
							logOut.Write(message.GetMessage())
							logOut.Write([]byte{'\n'})
						}
					}()

					err := natsRunner.MessageBus.PublishWithReplyTo(
						"diego.staging.start",
						"stager-test",
						stagingMessage,
					)
					Ω(err).ShouldNot(HaveOccurred())

					var payload []byte
					Eventually(payloads, 10.0).Should(Receive(&payload))
					Ω(string(payload)).Should(Equal(`{"error":"process exited with status 1"}`))

					expector := cmdtest.NewExpector(logIn, 5*time.Second)
					err = expector.Expect("no buildpack detected")
					Ω(err).ShouldNot(HaveOccurred())
				})
			})
		})

		Context("with two stagers running", func() {
			BeforeEach(func() {
				stagerRunner.Start("--compilers", `{"default":"smelter.zip"}`)
				otherStagerRunner.Start("--compilers", `{"default":"smelter.zip"}`)
			})

			AfterEach(func() {
				otherStagerRunner.Stop()
			})

			It("only one returns a staging completed response", func() {
				received := make(chan bool)
				natsRunner.MessageBus.Subscribe("two-stagers-test", func(message *yagnats.Message) {
					received <- true
				})

				natsRunner.MessageBus.PublishWithReplyTo(
					"diego.staging.start",
					"two-stagers-test",
					stagingMessage,
				)

				Eventually(received, LONG_TIMEOUT).Should(Receive())
				Consistently(received, SHORT_TIMEOUT).ShouldNot(Receive())
			})
		})
	})
})
<commit_msg>don't swallow nats errors in test<commit_after>package inigo_test

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"time"

	"github.com/cloudfoundry-incubator/inigo/loggredile"
	"github.com/fraenkel/candiedyaml"
	"github.com/vito/cmdtest"

	"github.com/cloudfoundry-incubator/inigo/archiver"
	"github.com/cloudfoundry-incubator/inigo/inigo_server"
	"github.com/cloudfoundry-incubator/inigo/stager_runner"
	"github.com/cloudfoundry-incubator/runtime-schema/models/factories"
	"github.com/cloudfoundry/yagnats"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Stager", func() {
	var otherStagerRunner *stager_runner.StagerRunner

	BeforeEach(func() {
		fileServerRunner.Start()
		otherStagerRunner = stager_runner.New(
			stagerPath,
			etcdRunner.NodeURLS(),
			[]string{fmt.Sprintf("127.0.0.1:%d", natsPort)},
		)
	})

	Context("when unable to find an appropriate compiler", func() {
		BeforeEach(func() {
			executorRunner.Start()
			stagerRunner.Start()
		})

		It("returns an error", func() {
			receivedMessages := make(chan *yagnats.Message)
			natsRunner.MessageBus.Subscribe("compiler-stagers-test", func(message *yagnats.Message) {
				receivedMessages <- message
			})

			natsRunner.MessageBus.PublishWithReplyTo(
				"diego.staging.start",
				"compiler-stagers-test",
				[]byte(`{
					"app_id": "some-app-guid",
					"task_id": "some-task-id",
					"app_bits_download_uri": "some-download-uri",
					"build_artifacts_cache_download_uri": "artifacts-download-uri",
					"build_artifacts_cache_upload_uri": "artifacts-upload-uri",
					"stack": "no-compiler"
				}`),
			)

			var receivedMessage *yagnats.Message
			Eventually(receivedMessages, SHORT_TIMEOUT).Should(Receive(&receivedMessage))
			Ω(receivedMessage.Payload).Should(ContainSubstring("no compiler defined for requested stack"))
			Consistently(receivedMessages, SHORT_TIMEOUT).ShouldNot(Receive())
		})
	})

	Describe("Staging", func() {
		var outputGuid string
		var stagingMessage []byte
		var buildpackToUse string

		BeforeEach(func() {
			executorRunner.Start()

			buildpackToUse = "admin_buildpack.zip"
			outputGuid = factories.GenerateGuid()

			fileServerRunner.ServeFile("smelter.zip", smelterZipPath)

			//make and upload an app
			var appFiles = []archiver.ArchiveFile{
				{"my-app", "scooby-doo"},
			}

			archiver.CreateZipArchive("/tmp/app.zip", appFiles)
			inigo_server.UploadFile("app.zip", "/tmp/app.zip")

			//make and upload a buildpack
			var adminBuildpackFiles = []archiver.ArchiveFile{
				{"bin/detect", `#!/bin/bash
echo My Buildpack
				`},
				{"bin/compile", `#!/bin/bash
echo $1 $2
echo COMPILING BUILDPACK
echo $SOME_STAGING_ENV
touch $1/compiled
touch $2/inserted-into-artifacts-cache
				`},
				{"bin/release", `#!/bin/bash
cat <<EOF
---
default_process_types:
  web: start-command
EOF
				`},
			}
			archiver.CreateZipArchive("/tmp/admin_buildpack.zip", adminBuildpackFiles)
			inigo_server.UploadFile("admin_buildpack.zip", "/tmp/admin_buildpack.zip")

			var bustedAdminBuildpackFiles = []archiver.ArchiveFile{
				{"bin/detect", `#!/bin/bash]
				exit 1
				`},
				{"bin/compile", `#!/bin/bash`},
				{"bin/release", `#!/bin/bash`},
			}

			archiver.CreateZipArchive("/tmp/busted_admin_buildpack.zip", bustedAdminBuildpackFiles)
			inigo_server.UploadFile("busted_admin_buildpack.zip", "/tmp/busted_admin_buildpack.zip")

			var buildArtifacts = []archiver.ArchiveFile{
				{"pulled-down-from-artifacts-cache", "some-contents-for-pulled-down-file"},
			}

			archiver.CreateTarGZArchive("/tmp/artifacts.tgz", buildArtifacts)
			inigo_server.UploadFile("artifacts.tgz", "/tmp/artifacts.tgz")
		})

		JustBeforeEach(func() {
			stagingMessage = []byte(
				fmt.Sprintf(
					`{
						"app_id": "some-app-guid",
						"task_id": "some-task-id",
						"memory_mb": 128,
						"disk_mb": 128,
						"file_descriptors": 1024,
						"stack": "default",
						"app_bits_download_uri": "%s",
						"build_artifacts_cache_download_uri": "%s",
						"build_artifacts_cache_upload_uri": "%s",
						"buildpacks" : [{ "key": "test-buildpack", "url": "%s" }],
						"environment": [["SOME_STAGING_ENV", "%s"]]
					}`,
					inigo_server.DownloadUrl("app.zip"),
					inigo_server.DownloadUrl("artifacts.tgz"),
					inigo_server.UploadUrl("uploaded-artifacts.tgz"),
					inigo_server.DownloadUrl(buildpackToUse),
					outputGuid,
				),
			)
		})

		Context("with one stager running", func() {
			BeforeEach(func() {
				stagerRunner.Start("--compilers", `{"default":"smelter.zip"}`)
			})

			It("runs the compiler on the executor with the correct environment variables, bits and log tag, and responds with the detected buildpack", func() {
				//listen for NATS response
				payloads := make(chan []byte)

				natsRunner.MessageBus.Subscribe("stager-test", func(msg *yagnats.Message) {
					payloads <- msg.Payload
				})

				//stream logs
				messages, stop := loggredile.StreamMessages(
					loggregatorRunner.Config.OutgoingPort,
					"/tail/?app=some-app-guid",
				)
				defer close(stop)

				logOutput := ""
				go func() {
					for message := range messages {
						Ω(message.GetSourceName()).To(Equal("STG"))
						logOutput += string(message.GetMessage()) + "\n"
					}
				}()

				//publish the staging message
				err := natsRunner.MessageBus.PublishWithReplyTo(
					"diego.staging.start",
					"stager-test",
					stagingMessage,
				)
				Ω(err).ShouldNot(HaveOccurred())

				//wait for staging to complete
				var payload []byte
				Eventually(payloads, LONG_TIMEOUT).Should(Receive(&payload))

				//Assert on the staging output (detected buildpack)
				Ω(string(payload)).Should(Equal(`{"detected_buildpack":"My Buildpack"}`))

				//Asser the user saw reasonable output
				Eventually(func() string {
					return logOutput
				}).Should(ContainSubstring("COMPILING BUILDPACK"))
				Ω(logOutput).Should(ContainSubstring(outputGuid))

				// Assert that the build artifacts cache was downloaded

				// Assert that the build artifacts cache was uploaded again
				artifactsCache, err := gzip.NewReader(inigo_server.DownloadFile("uploaded-artifacts.tgz"))
				Ω(err).ShouldNot(HaveOccurred())

				untarredBuildArtifactsData := tar.NewReader(artifactsCache)
				buildArtifactContents := map[string][]byte{}
				for {
					hdr, err := untarredBuildArtifactsData.Next()
					if err == io.EOF {
						break
					}

					Ω(err).ShouldNot(HaveOccurred())

					content, err := ioutil.ReadAll(untarredBuildArtifactsData)
					Ω(err).ShouldNot(HaveOccurred())

					buildArtifactContents[hdr.Name] = content
				}

				Ω(buildArtifactContents).Should(HaveKey("pulled-down-from-artifacts-cache"))
				Ω(buildArtifactContents).Should(HaveKey("inserted-into-artifacts-cache"))

				//Fetch the compiled droplet from the fakeCC
				dropletData, ok := fakeCC.UploadedDroplets["some-app-guid"]
				Ω(ok).Should(BeTrue())
				Ω(dropletData).ShouldNot(BeEmpty())

				//Unzip the droplet
				ungzippedDropletData, err := gzip.NewReader(bytes.NewReader(dropletData))
				Ω(err).ShouldNot(HaveOccurred())

				//Untar the droplet
				untarredDropletData := tar.NewReader(ungzippedDropletData)
				dropletContents := map[string][]byte{}
				for {
					hdr, err := untarredDropletData.Next()
					if err == io.EOF {
						break
					}
					Ω(err).ShouldNot(HaveOccurred())

					content, err := ioutil.ReadAll(untarredDropletData)
					Ω(err).ShouldNot(HaveOccurred())

					dropletContents[hdr.Name] = content
				}

				//Assert the droplet has the right files in it
				Ω(dropletContents).Should(HaveKey("staging_info.yml"))
				Ω(dropletContents).Should(HaveKey("logs/"))
				Ω(dropletContents).Should(HaveKey("tmp/"))
				Ω(dropletContents).Should(HaveKey("app/"))
				Ω(dropletContents).Should(HaveKey("app/my-app"))
				Ω(dropletContents).Should(HaveKey("app/compiled"))

				//Assert the files contain the right content
				Ω(string(dropletContents["app/my-app"])).Should(Equal("scooby-doo"))

				//In particular, staging_info.yml should have the correct detected_buildpack and start_command
				yamlDecoder := candiedyaml.NewDecoder(bytes.NewReader(dropletContents["staging_info.yml"]))
				stagingInfo := map[string]string{}
				err = yamlDecoder.Decode(&stagingInfo)
				Ω(err).ShouldNot(HaveOccurred())

				Ω(stagingInfo["detected_buildpack"]).Should(Equal("My Buildpack"))
				Ω(stagingInfo["start_command"]).Should(Equal("start-command"))

				//Assert nothing else crept into the droplet
				Ω(dropletContents).Should(HaveLen(6))
			})

			Context("when compilation fails", func() {
				BeforeEach(func() {
					buildpackToUse = "busted_admin_buildpack.zip"
				})

				It("responds with the error, and no detected buildpack present", func() {
					payloads := make(chan []byte)

					natsRunner.MessageBus.Subscribe("stager-test", func(msg *yagnats.Message) {
						payloads <- msg.Payload
					})

					messages, stop := loggredile.StreamMessages(
						loggregatorRunner.Config.OutgoingPort,
						"/tail/?app=some-app-guid",
					)
					defer close(stop)

					logIn, logOut := io.Pipe()
					go func() {
						for message := range messages {
							Ω(message.GetSourceName()).To(Equal("STG"))
							logOut.Write(message.GetMessage())
							logOut.Write([]byte{'\n'})
						}
					}()

					err := natsRunner.MessageBus.PublishWithReplyTo(
						"diego.staging.start",
						"stager-test",
						stagingMessage,
					)
					Ω(err).ShouldNot(HaveOccurred())

					var payload []byte
					Eventually(payloads, 10.0).Should(Receive(&payload))
					Ω(string(payload)).Should(Equal(`{"error":"process exited with status 1"}`))

					expector := cmdtest.NewExpector(logIn, 5*time.Second)
					err = expector.Expect("no buildpack detected")
					Ω(err).ShouldNot(HaveOccurred())
				})
			})
		})

		Context("with two stagers running", func() {
			BeforeEach(func() {
				stagerRunner.Start("--compilers", `{"default":"smelter.zip"}`)
				otherStagerRunner.Start("--compilers", `{"default":"smelter.zip"}`)
			})

			AfterEach(func() {
				otherStagerRunner.Stop()
			})

			It("only one returns a staging completed response", func() {
				received := make(chan bool)

				_, err := natsRunner.MessageBus.Subscribe("two-stagers-test", func(message *yagnats.Message) {
					received <- true
				})
				Ω(err).ShouldNot(HaveOccurred())

				err = natsRunner.MessageBus.PublishWithReplyTo(
					"diego.staging.start",
					"two-stagers-test",
					stagingMessage,
				)
				Ω(err).ShouldNot(HaveOccurred())

				Eventually(received, LONG_TIMEOUT).Should(Receive())
				Consistently(received, SHORT_TIMEOUT).ShouldNot(Receive())
			})
		})
	})
})
<|endoftext|>


<commit_before>package stash

import (
	"fmt"
)

type Project struct {
	Key string `"json:key"`
}

type Repo struct {
	Name    string `"json:name"`
	Slug    string `"json:slug"`
	Public  bool   `"json:public"`
	Project *Project
}

type RepoResource struct {
	client *Client
}

// Get the named repository
func (r *RepoResource) Find(project, slug string) (*Repo, error) {
	repo := Repo{}
	path := fmt.Sprintf("/projects/%s/repos/%s", project, slug)

	if err := r.client.do("GET", "core", path, nil, nil, &repo); err != nil {
		return nil, err
	}

	return &repo, nil
}
<commit_msg>added get repos list method<commit_after>package stash

import (
	"fmt"
	"net/url"
)

type Project struct {
	Key string `"json:key"`
}

type Repo struct {
	Name    string   `"json:name"`
	Slug    string   `"json:slug"`
	Public  bool     `"json:public"`
	ScmId   string   `"json:scmId"`
	Project *Project `"json:project"`
}

type Response struct {
	Values        []Repo `"json:values"`
	Size          int    `"json:size"`
	NextPageStart int    `"json:nextPageStart"`
	IsLastPage    bool   `"json:isLastPage"`
}

type RepoResource struct {
	client *Client
}

// Get list of repositories
func (r *RepoResource) List() ([]Repo, error) {
	repos := []Repo{}
	response := Response{}
	path := "/repos"
	params := url.Values{}

	for {
		if response.NextPageStart != 0 {
			params.Set("start", fmt.Sprintf("%d", response.NextPageStart))
		}

		if err := r.client.do("GET", "core", path, params, nil, &response); err != nil {
			return nil, err
		}

		repos = append(repos, response.Values...)

		if response.IsLastPage {
			break
		}
	}

	return repos, nil
}

// Get the named repository
func (r *RepoResource) Find(project, slug string) (*Repo, error) {
	repo := Repo{}
	path := fmt.Sprintf("/projects/%s/repos/%s", project, slug)

	if err := r.client.do("GET", "core", path, nil, nil, &repo); err != nil {
		return nil, err
	}

	return &repo, nil
}
<|endoftext|>


<commit_before>// Package whitelist implements IP whitelisting for various types
// of connections.
package whitelist

import (
	"errors"
	"log"
	"net"
	"sort"
	"strings"
	"sync"
)

// An ACL stores a list of permitted IP addresses, and handles
// concurrency as needed.
type ACL interface {
	// Permitted takes an IP address, and returns true if the
	// IP address is whitelisted (e.g. permitted access).
	Permitted(net.IP) bool

	// Add takes an IP address and adds it to the whitelist so
	// that it is now permitted.
	Add(net.IP)

	// Remove takes an IP address and drops it from the whitelist
	// so that it is no longer permitted.
	Remove(net.IP)
}

func validIP(ip net.IP) bool {
	if len(ip) == 4 {
		return true
	}

	if len(ip) == 16 {
		return true
	}

	return false
}

// Basic implements a basic map-backed whitelister that uses an
// RWMutex for conccurency. IPv4 addresses are treated differently
// than an IPv6 address; namely, the IPv4 localhost will not match
// the IPv6 localhost.
type Basic struct {
	lock   sync.RWMutex
	ipList map[string]bool
}

// Permitted returns true if the IP has been whitelisted.
func (wl *Basic) Permitted(ip net.IP) bool {
	if !validIP(ip) {
		return false
	}

	wl.lock.RLock()
	defer wl.lock.RUnlock()
	return wl.ipList[ip.String()]
}

// Add whitelists an IP.
func (wl *Basic) Add(ip net.IP) {
	if !validIP(ip) {
		return
	}

	wl.lock.Lock()
	defer wl.lock.Unlock()
	wl.ipList[ip.String()] = true
}

// Remove clears the IP from the whitelist.
func (wl *Basic) Remove(ip net.IP) {
	if !validIP(ip) {
		return
	}

	wl.lock.Lock()
	defer wl.lock.Unlock()
	delete(wl.ipList, ip.String())
}

// NewBasic returns a new initialised basic whitelist.
func NewBasic() *Basic {
	return &Basic{
		ipList: map[string]bool{},
	}
}

// DumpBasic returns a whitelist as a byte slice where each IP is on
// its own line.
func DumpBasic(wl *Basic) []byte {
	wl.lock.RLock()
	defer wl.lock.RUnlock()

	var addrs = make([]string, 0, len(wl.ipList))
	for ip := range wl.ipList {
		addrs = append(addrs, ip)
	}

	sort.Strings(addrs)

	addrList := strings.Join(addrs, "\n")
	return []byte(addrList)
}

// LoadBasic loads a whitelist from a byteslice.
func LoadBasic(in []byte) (*Basic, error) {
	wl := NewBasic()
	addrs := strings.Split(string(in), "\n")

	for _, addr := range addrs {
		ip := net.ParseIP(addr)
		if ip == nil {
			return nil, errors.New("whitelist: invalid address")
		}
		wl.Add(ip)
	}
	return wl, nil
}

// Stub allows whitelisting to be added into a system's flow
// without doing anything yet. All operations result in warning log
// messages being printed to stderr. There is no mechanism for
// squelching these messages short of modifying the log package's
// default logger.
type Stub struct{}

// Permitted always returns true, but prints a warning message alerting
// that whitelisting is stubbed.
func (wl *Stub) Permitted(ip net.IP) bool {
	log.Printf("WARNING: whitelist check for %s but whitelisting is stubbed", ip)
	return true
}

// Add prints a warning message about whitelisting being stubbed.
func (wl *Stub) Add(ip net.IP) {
	log.Printf("WARNING: IP %s added to whitelist but whitelisting is stubbed", ip)
}

// Remove prints a warning message about whitelisting being stubbed.
func (wl *Stub) Remove(ip net.IP) {
	log.Printf("WARNING: IP %s removed from whitelist but whitelisting is stubbed", ip)
}

// NewStub returns a new stubbed whitelister.
func NewStub() *Stub {
	log.Println("WARNING: whitelisting is being stubbed")
	return &Stub{}
}
<commit_msg>Switch to Mutex in Basic<commit_after>// Package whitelist implements IP whitelisting for various types
// of connections.
package whitelist

import (
	"errors"
	"log"
	"net"
	"sort"
	"strings"
	"sync"
)

// An ACL stores a list of permitted IP addresses, and handles
// concurrency as needed.
type ACL interface {
	// Permitted takes an IP address, and returns true if the
	// IP address is whitelisted (e.g. permitted access).
	Permitted(net.IP) bool

	// Add takes an IP address and adds it to the whitelist so
	// that it is now permitted.
	Add(net.IP)

	// Remove takes an IP address and drops it from the whitelist
	// so that it is no longer permitted.
	Remove(net.IP)
}

func validIP(ip net.IP) bool {
	if len(ip) == 4 {
		return true
	}

	if len(ip) == 16 {
		return true
	}

	return false
}

// Basic implements a basic map-backed whitelister that uses an
// RWMutex for conccurency. IPv4 addresses are treated differently
// than an IPv6 address; namely, the IPv4 localhost will not match
// the IPv6 localhost.
type Basic struct {
	lock   *sync.Mutex
	ipList map[string]bool
}

// Permitted returns true if the IP has been whitelisted.
func (wl *Basic) Permitted(ip net.IP) bool {
	if !validIP(ip) {
		return false
	}

	wl.lock.Lock()
	permitted := wl.ipList[ip.String()]
	wl.lock.Unlock()
	return permitted
}

// Add whitelists an IP.
func (wl *Basic) Add(ip net.IP) {
	if !validIP(ip) {
		return
	}

	wl.lock.Lock()
	defer wl.lock.Unlock()
	wl.ipList[ip.String()] = true
}

// Remove clears the IP from the whitelist.
func (wl *Basic) Remove(ip net.IP) {
	if !validIP(ip) {
		return
	}

	wl.lock.Lock()
	defer wl.lock.Unlock()
	delete(wl.ipList, ip.String())
}

// NewBasic returns a new initialised basic whitelist.
func NewBasic() *Basic {
	return &Basic{
		lock:   new(sync.Mutex),
		ipList: map[string]bool{},
	}
}

// DumpBasic returns a whitelist as a byte slice where each IP is on
// its own line.
func DumpBasic(wl *Basic) []byte {
	wl.lock.Lock()
	defer wl.lock.Unlock()

	var addrs = make([]string, 0, len(wl.ipList))
	for ip := range wl.ipList {
		addrs = append(addrs, ip)
	}

	sort.Strings(addrs)

	addrList := strings.Join(addrs, "\n")
	return []byte(addrList)
}

// LoadBasic loads a whitelist from a byteslice.
func LoadBasic(in []byte) (*Basic, error) {
	wl := NewBasic()
	addrs := strings.Split(string(in), "\n")

	for _, addr := range addrs {
		ip := net.ParseIP(addr)
		if ip == nil {
			return nil, errors.New("whitelist: invalid address")
		}
		wl.Add(ip)
	}
	return wl, nil
}

// Stub allows whitelisting to be added into a system's flow
// without doing anything yet. All operations result in warning log
// messages being printed to stderr. There is no mechanism for
// squelching these messages short of modifying the log package's
// default logger.
type Stub struct{}

// Permitted always returns true, but prints a warning message alerting
// that whitelisting is stubbed.
func (wl *Stub) Permitted(ip net.IP) bool {
	log.Printf("WARNING: whitelist check for %s but whitelisting is stubbed", ip)
	return true
}

// Add prints a warning message about whitelisting being stubbed.
func (wl *Stub) Add(ip net.IP) {
	log.Printf("WARNING: IP %s added to whitelist but whitelisting is stubbed", ip)
}

// Remove prints a warning message about whitelisting being stubbed.
func (wl *Stub) Remove(ip net.IP) {
	log.Printf("WARNING: IP %s removed from whitelist but whitelisting is stubbed", ip)
}

// NewStub returns a new stubbed whitelister.
func NewStub() *Stub {
	log.Println("WARNING: whitelisting is being stubbed")
	return &Stub{}
}
<|endoftext|>