<commit_before>// +build appengine

/*

A Google App Engine Memcache session store implementation.

The implementation stores sessions in the Memcache and also saves sessions to the Datastore as a backup
in case data would be removed from the Memcache. This behaviour is optional, Datastore can be disabled completely.
You can also choose whether saving to Datastore happens synchronously (in the same goroutine)
or asynchronously (in another goroutine).

Limitations based on GAE Memcache:

- Since session ids are used in the Memcache keys, session ids can't be longer than 250 chars (bytes, but with Base64 charset it's the same).
If you also specify a key prefix (in MemcacheStoreOptions), that also counts into it.

- The size of a Session cannot be larger than 1 MB (marshalled into a byte slice).

Note that the Store will automatically "flush" sessions accessed from it when the Store is closed,
so it is very important to close the Store at the end of your request; this is usually done by closing
the session manager to which you passed the store (preferably with the defer statement).

Check out the GAE session demo application which shows how to use it properly:

https://github.com/icza/session/blob/master/gae_session_demo/session_demo.go

*/

package session

import (
	"net/http"
	"sync"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/memcache"
	"golang.org/x/net/context"
	"google.golang.org/appengine/log"
)

// A Google App Engine Memcache session store implementation.
type memcacheStore struct {
	ctx context.Context // Appengine context used when accessing the Memcache

	keyPrefix string // Prefix to use in front of session ids to construct Memcache key
	retries   int    // Number of retries to perform in case of general Memcache failures

	codec memcache.Codec // Codec used to marshal and unmarshal a Session to a byte slice

	onlyMemcache       bool   // Tells if sessions are not to be saved in Datastore
	asyncDatastoreSave bool   // Tells if saving in Datastore should happen asynchronously, in a new goroutine
	dsEntityName       string // Name of the datastore entity to use to save sessions

	// Map of sessions (mapped from ID) that were accessed using this store; usually it will only be 1.
	// It is also used as a cache, should the user call Get() with the same id multiple times.
	sessions map[string]Session

	mux *sync.RWMutex // mutex to synchronize access to sessions
}

// MemcacheStoreOptions defines options that may be passed when creating a new Memcache session store.
// All fields are optional; default value will be used for any field that has the zero value.
type MemcacheStoreOptions struct {
	// Prefix to use when storing sessions in the Memcache, cannot contain a null byte
	// and cannot be longer than 250 chars (bytes) when concatenated with the session id; default value is the empty string
	// The Memcache key will be this prefix and the session id concatenated.
	KeyPrefix string

	// Number of retries to perform if Memcache operations fail due to general service error;
	// default value is 3
	Retries int

	// Codec used to marshal and unmarshal a Session to a byte slice;
	// Default value is &memcache.Gob (which uses the gob package).
	Codec *memcache.Codec

	// Tells if sessions are only to be stored in Memcache, and do not store them in Datastore as backup;
	// as Memcache has no guarantees, it may lose content from time to time, but if Datastore is
	// also used, the session will automatically be retrieved from the Datastore if not found in Memcache;
	// default value is false (which means to also save sessions in the Datastore)
	OnlyMemcache bool

	// Tells if saving in Datastore should happen asynchronously (in a new goroutine, possibly after returning),
	// if false, session saving in Datastore will happen in the same goroutine, before returning from the request.
	// Asynchronous saving gives smaller latency (and is enough most of the time as Memcache is always checked first);
	// default value is false which means to save sessions in the Datastore in the same goroutine, synchronously
	// Not used if OnlyMemcache=true.
	// FIXME: See https://github.com/icza/session/issues/3
	AsyncDatastoreSave bool

	// Name of the entity to use for saving sessions;
	// default value is "sess_"
	// Not used if OnlyMemcache=true.
	DSEntityName string
}

// SessEntity models the session entity saved to Datastore.
// The Key is the session id.
type SessEntity struct {
	Expires time.Time `datastore:"exp"`
	Value   []byte    `datastore:"val"`
}

// Pointer to zero value of MemcacheStoreOptions to be reused for efficiency.
var zeroMemcacheStoreOptions = new(MemcacheStoreOptions)

// NewMemcacheStore returns a new, GAE Memcache session Store with default options.
// Default values of options are listed in the MemcacheStoreOptions type.
//
// Important! Since accessing the Memcache relies on Appengine Context
// which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
func NewMemcacheStore(ctx context.Context) Store {
	return NewMemcacheStoreOptions(ctx, zeroMemcacheStoreOptions)
}

const defaultDSEntityName = "sess_" // Default value of DSEntityName.

// NewMemcacheStoreOptions returns a new, GAE Memcache session Store with the specified options.
//
// Important! Since accessing the Memcache relies on Appengine Context
// which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
func NewMemcacheStoreOptions(ctx context.Context, o *MemcacheStoreOptions) Store {
	s := &memcacheStore{
		ctx:                ctx,
		keyPrefix:          o.KeyPrefix,
		retries:            o.Retries,
		onlyMemcache:       o.OnlyMemcache,
		asyncDatastoreSave: o.AsyncDatastoreSave,
		dsEntityName:       o.DSEntityName,
		sessions:           make(map[string]Session, 2),
		mux:                &sync.RWMutex{},
	}
	if s.retries <= 0 {
		s.retries = 3
	}
	if o.Codec != nil {
		s.codec = *o.Codec
	} else {
		s.codec = memcache.Gob
	}
	if s.dsEntityName == "" {
		s.dsEntityName = defaultDSEntityName
	}
	return s
}

// Get is to implement Store.Get().
// Important! Since sessions are marshalled and stored in the Memcache,
// the mutex of the Session (Session.RWMutex()) will be different for each
// Session value (even though they might have the same session id)!
func (s *memcacheStore) Get(id string) Session {
	s.mux.RLock()
	defer s.mux.RUnlock()

	// First check our "cache"
	if sess := s.sessions[id]; sess != nil {
		return sess
	}

	// Next check in Memcache
	var err error
	var sess *sessionImpl

	for i := 0; i < s.retries; i++ {
		var sess_ sessionImpl
		_, err = s.codec.Get(s.ctx, s.keyPrefix+id, &sess_)
		if err == memcache.ErrCacheMiss {
			break // It's not in the Memcache (e.g. invalid sess id or was removed from Memcache by AppEngine)
		}
		if err == nil {
			sess = &sess_
			break
		}
		// Service error? Retry..
	}

	if sess == nil {
		if err != nil && err != memcache.ErrCacheMiss {
			log.Errorf(s.ctx, "Failed to get session from memcache, id: %s, error: %v", id, err)
		}

		// Ok, we didn't get it from Memcace (either was not there or Memcache service is unavailable).
		// Now it's time to check in the Datastore.
		key := datastore.NewKey(s.ctx, s.dsEntityName, id, 0, nil)
		for i := 0; i < s.retries; i++ {
			e := SessEntity{}
			err = datastore.Get(s.ctx, key, &e)
			if err == datastore.ErrNoSuchEntity {
				return nil // It's not in the Datastore either
			}
			if err != nil {
				// Service error? Retry..
				continue
			}
			if e.Expires.Before(time.Now()) {
				// Session expired.
				datastore.Delete(s.ctx, key) // Omitting error check...
				return nil
			}
			var sess_ sessionImpl
			if err = s.codec.Unmarshal(e.Value, &sess_); err != nil {
				break // Invalid data in stored session entity...
			}
			sess = &sess_
			break
		}
	}

	if sess == nil {
		log.Errorf(s.ctx, "Failed to get session from datastore, id: %s, error: %v", id, err)
		return nil
	}

	// Yes! We have it! "Actualize" it.
	sess.Access()
	// Mutex is not marshalled, so create a new one:
	sess.mux = &sync.RWMutex{}
	s.sessions[id] = sess
	return sess
}

// Add is to implement Store.Add().
func (s *memcacheStore) Add(sess Session) {
	s.mux.Lock()
	defer s.mux.Unlock()

	if s.setMemcacheSession(sess) {
		log.Infof(s.ctx, "Session added: %s", sess.ID())
		s.sessions[sess.ID()] = sess
		return
	}
}

// setMemcacheSession sets the specified session in the Memcache.
func (s *memcacheStore) setMemcacheSession(sess Session) (success bool) {
	item := &memcache.Item{
		Key:        s.keyPrefix + sess.ID(),
		Object:     sess,
		Expiration: sess.Timeout(),
	}

	var err error
	for i := 0; i < s.retries; i++ {
		if err = s.codec.Set(s.ctx, item); err == nil {
			return true
		}
	}

	log.Errorf(s.ctx, "Failed to add session to memcache, id: %s, error: %v", sess.ID(), err)
	return false
}

// Remove is to implement Store.Remove().
func (s *memcacheStore) Remove(sess Session) {
	s.mux.Lock()
	defer s.mux.Unlock()

	var err error
	for i := 0; i < s.retries; i++ {
		if err = memcache.Delete(s.ctx, s.keyPrefix+sess.ID()); err == nil || err == memcache.ErrCacheMiss {
			log.Infof(s.ctx, "Session removed: %s", sess.ID())
			delete(s.sessions, sess.ID())
			if !s.onlyMemcache {
				// Also from the Datastore:
				key := datastore.NewKey(s.ctx, s.dsEntityName, sess.ID(), 0, nil)
				datastore.Delete(s.ctx, key) // Omitting error check...
			}
			return
		}
	}
	log.Errorf(s.ctx, "Failed to remove session from memcache, id: %s, error: %v", sess.ID(), err)
}

// Close is to implement Store.Close().
func (s *memcacheStore) Close() {
	// Flush out sessions that were accessed from this store. No need locking, we're closing...
	// We could use Cocec.SetMulti(), but sessions will contain at most 1 session like all the times.
	for _, sess := range s.sessions {
		s.setMemcacheSession(sess)
	}

	if s.onlyMemcache {
		return // Don't save to Datastore
	}

	if s.asyncDatastoreSave {
		go s.saveToDatastore()
	} else {
		s.saveToDatastore()
	}
}

// saveToDatastore saves the sessions of the Store to the Datastore
// in the caller's goroutine.
func (s *memcacheStore) saveToDatastore() {
	// Save sessions that were accessed from this store. No need locking, we're closing...
	// We could use datastore.PutMulti(), but sessions will contain at most 1 session like all the times.
	for _, sess := range s.sessions {
		value, err := s.codec.Marshal(sess)
		if err != nil {
			log.Errorf(s.ctx, "Failed to marshal session: %s, error: %v", sess.ID(), err)
			continue
		}
		e := SessEntity{
			Expires: sess.Accessed().Add(sess.Timeout()),
			Value:   value,
		}
		key := datastore.NewKey(s.ctx, s.dsEntityName, sess.ID(), 0, nil)
		for i := 0; i < s.retries; i++ {
			if _, err = datastore.Put(s.ctx, key, &e); err == nil {
				break
			}
		}
		if err != nil {
			log.Errorf(s.ctx, "Failed to save session to datastore: %s, error: %v", sess.ID(), err)
		}
	}
}

// PurgeExpiredSessFromDSFunc returns a request handler function which deletes expired sessions
// from the Datastore.
// dsEntityName is the name of the entity used for saving sessions; pass an empty string
// to use the default value (which is "sess_").
//
// It is recommended to register the returned handler function to a path which then can be defined
// as a cron job to be called periodically, e.g. in every 30 minutes or so (your choice).
// As cron handlers may run up to 10 minutes, the returned handler will stop at 8 minutes
// to complete safely even if there are more expired, undeleted sessions.
//
// The response of the handler func is a JSON text telling if the handler was able to delete all expired sessions,
// or that it was finished early due to the time. Examle of a respone where all expired sessions were deleted:
//
//     {"completed":true}
func PurgeExpiredSessFromDSFunc(dsEntityName string) http.HandlerFunc {
	if dsEntityName == "" {
		dsEntityName = defaultDSEntityName
	}

	return func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)
		// Delete in batches of 100
		q := datastore.NewQuery(dsEntityName).Filter("exp<", time.Now()).KeysOnly().Limit(100)

		deadline := time.Now().Add(time.Minute * 8)

		for {
			var err error
			var keys []*datastore.Key

			if keys, err = q.GetAll(c, nil); err != nil {
				// Datastore error.
				log.Errorf(c, "Failed to query expired sessions: %v", err)
				http.Error(w, "Failed to query expired sessions!", http.StatusInternalServerError)
			}
			if len(keys) == 0 {
				// We're done, no more expired sessions
				w.Header().Set("Content-Type", "application/json")
				w.Write([]byte(`{"completed":true}`))
				return
			}

			if err = datastore.DeleteMulti(c, keys); err != nil {
				log.Errorf(c, "Error while deleting expired sessions: %v", err)
			}

			if time.Now().After(deadline) {
				// Our time is up, return
				w.Header().Set("Content-Type", "application/json")
				w.Write([]byte(`{"completed":false}`))
				return
			}
			// We have time to continue
		}
	}
}
<commit_msg>Fixed a problem where sess.mux caused panic with nil<commit_after>// +build appengine

/*

A Google App Engine Memcache session store implementation.

The implementation stores sessions in the Memcache and also saves sessions to the Datastore as a backup
in case data would be removed from the Memcache. This behaviour is optional, Datastore can be disabled completely.
You can also choose whether saving to Datastore happens synchronously (in the same goroutine)
or asynchronously (in another goroutine).

Limitations based on GAE Memcache:

- Since session ids are used in the Memcache keys, session ids can't be longer than 250 chars (bytes, but with Base64 charset it's the same).
If you also specify a key prefix (in MemcacheStoreOptions), that also counts into it.

- The size of a Session cannot be larger than 1 MB (marshalled into a byte slice).

Note that the Store will automatically "flush" sessions accessed from it when the Store is closed,
so it is very important to close the Store at the end of your request; this is usually done by closing
the session manager to which you passed the store (preferably with the defer statement).

Check out the GAE session demo application which shows how to use it properly:

https://github.com/icza/session/blob/master/gae_session_demo/session_demo.go

*/

package session

import (
	"net/http"
	"sync"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/memcache"
	"golang.org/x/net/context"
	"google.golang.org/appengine/log"
)

// A Google App Engine Memcache session store implementation.
type memcacheStore struct {
	ctx context.Context // Appengine context used when accessing the Memcache

	keyPrefix string // Prefix to use in front of session ids to construct Memcache key
	retries   int    // Number of retries to perform in case of general Memcache failures

	codec memcache.Codec // Codec used to marshal and unmarshal a Session to a byte slice

	onlyMemcache       bool   // Tells if sessions are not to be saved in Datastore
	asyncDatastoreSave bool   // Tells if saving in Datastore should happen asynchronously, in a new goroutine
	dsEntityName       string // Name of the datastore entity to use to save sessions

	// Map of sessions (mapped from ID) that were accessed using this store; usually it will only be 1.
	// It is also used as a cache, should the user call Get() with the same id multiple times.
	sessions map[string]Session

	mux *sync.RWMutex // mutex to synchronize access to sessions
}

// MemcacheStoreOptions defines options that may be passed when creating a new Memcache session store.
// All fields are optional; default value will be used for any field that has the zero value.
type MemcacheStoreOptions struct {
	// Prefix to use when storing sessions in the Memcache, cannot contain a null byte
	// and cannot be longer than 250 chars (bytes) when concatenated with the session id; default value is the empty string
	// The Memcache key will be this prefix and the session id concatenated.
	KeyPrefix string

	// Number of retries to perform if Memcache operations fail due to general service error;
	// default value is 3
	Retries int

	// Codec used to marshal and unmarshal a Session to a byte slice;
	// Default value is &memcache.Gob (which uses the gob package).
	Codec *memcache.Codec

	// Tells if sessions are only to be stored in Memcache, and do not store them in Datastore as backup;
	// as Memcache has no guarantees, it may lose content from time to time, but if Datastore is
	// also used, the session will automatically be retrieved from the Datastore if not found in Memcache;
	// default value is false (which means to also save sessions in the Datastore)
	OnlyMemcache bool

	// Tells if saving in Datastore should happen asynchronously (in a new goroutine, possibly after returning),
	// if false, session saving in Datastore will happen in the same goroutine, before returning from the request.
	// Asynchronous saving gives smaller latency (and is enough most of the time as Memcache is always checked first);
	// default value is false which means to save sessions in the Datastore in the same goroutine, synchronously
	// Not used if OnlyMemcache=true.
	// FIXME: See https://github.com/icza/session/issues/3
	AsyncDatastoreSave bool

	// Name of the entity to use for saving sessions;
	// default value is "sess_"
	// Not used if OnlyMemcache=true.
	DSEntityName string
}

// SessEntity models the session entity saved to Datastore.
// The Key is the session id.
type SessEntity struct {
	Expires time.Time `datastore:"exp"`
	Value   []byte    `datastore:"val"`
}

// Pointer to zero value of MemcacheStoreOptions to be reused for efficiency.
var zeroMemcacheStoreOptions = new(MemcacheStoreOptions)

// NewMemcacheStore returns a new, GAE Memcache session Store with default options.
// Default values of options are listed in the MemcacheStoreOptions type.
//
// Important! Since accessing the Memcache relies on Appengine Context
// which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
func NewMemcacheStore(ctx context.Context) Store {
	return NewMemcacheStoreOptions(ctx, zeroMemcacheStoreOptions)
}

const defaultDSEntityName = "sess_" // Default value of DSEntityName.

// NewMemcacheStoreOptions returns a new, GAE Memcache session Store with the specified options.
//
// Important! Since accessing the Memcache relies on Appengine Context
// which is bound to an http.Request, the returned Store can only be used for the lifetime of a request!
func NewMemcacheStoreOptions(ctx context.Context, o *MemcacheStoreOptions) Store {
	s := &memcacheStore{
		ctx:                ctx,
		keyPrefix:          o.KeyPrefix,
		retries:            o.Retries,
		onlyMemcache:       o.OnlyMemcache,
		asyncDatastoreSave: o.AsyncDatastoreSave,
		dsEntityName:       o.DSEntityName,
		sessions:           make(map[string]Session, 2),
		mux:                &sync.RWMutex{},
	}
	if s.retries <= 0 {
		s.retries = 3
	}
	if o.Codec != nil {
		s.codec = *o.Codec
	} else {
		s.codec = memcache.Gob
	}
	if s.dsEntityName == "" {
		s.dsEntityName = defaultDSEntityName
	}
	return s
}

// Get is to implement Store.Get().
// Important! Since sessions are marshalled and stored in the Memcache,
// the mutex of the Session (Session.RWMutex()) will be different for each
// Session value (even though they might have the same session id)!
func (s *memcacheStore) Get(id string) Session {
	s.mux.RLock()
	defer s.mux.RUnlock()

	// First check our "cache"
	if sess := s.sessions[id]; sess != nil {
		return sess
	}

	// Next check in Memcache
	var err error
	var sess *sessionImpl

	for i := 0; i < s.retries; i++ {
		var sess_ sessionImpl
		_, err = s.codec.Get(s.ctx, s.keyPrefix+id, &sess_)
		if err == memcache.ErrCacheMiss {
			break // It's not in the Memcache (e.g. invalid sess id or was removed from Memcache by AppEngine)
		}
		if err == nil {
			sess = &sess_
			break
		}
		// Service error? Retry..
	}

	if sess == nil {
		if err != nil && err != memcache.ErrCacheMiss {
			log.Errorf(s.ctx, "Failed to get session from memcache, id: %s, error: %v", id, err)
		}

		// Ok, we didn't get it from Memcace (either was not there or Memcache service is unavailable).
		// Now it's time to check in the Datastore.
		key := datastore.NewKey(s.ctx, s.dsEntityName, id, 0, nil)
		for i := 0; i < s.retries; i++ {
			e := SessEntity{}
			err = datastore.Get(s.ctx, key, &e)
			if err == datastore.ErrNoSuchEntity {
				return nil // It's not in the Datastore either
			}
			if err != nil {
				// Service error? Retry..
				continue
			}
			if e.Expires.Before(time.Now()) {
				// Session expired.
				datastore.Delete(s.ctx, key) // Omitting error check...
				return nil
			}
			var sess_ sessionImpl
			if err = s.codec.Unmarshal(e.Value, &sess_); err != nil {
				break // Invalid data in stored session entity...
			}
			sess = &sess_
			break
		}
	}

	if sess == nil {
		log.Errorf(s.ctx, "Failed to get session from datastore, id: %s, error: %v", id, err)
		return nil
	}

	// Mutex is not marshalled, so create a new one:
	sess.mux = &sync.RWMutex{}
	// Yes! We have it! "Actualize" it.
	sess.Access()
	s.sessions[id] = sess
	return sess
}

// Add is to implement Store.Add().
func (s *memcacheStore) Add(sess Session) {
	s.mux.Lock()
	defer s.mux.Unlock()

	if s.setMemcacheSession(sess) {
		log.Infof(s.ctx, "Session added: %s", sess.ID())
		s.sessions[sess.ID()] = sess
		return
	}
}

// setMemcacheSession sets the specified session in the Memcache.
func (s *memcacheStore) setMemcacheSession(sess Session) (success bool) {
	item := &memcache.Item{
		Key:        s.keyPrefix + sess.ID(),
		Object:     sess,
		Expiration: sess.Timeout(),
	}

	var err error
	for i := 0; i < s.retries; i++ {
		if err = s.codec.Set(s.ctx, item); err == nil {
			return true
		}
	}

	log.Errorf(s.ctx, "Failed to add session to memcache, id: %s, error: %v", sess.ID(), err)
	return false
}

// Remove is to implement Store.Remove().
func (s *memcacheStore) Remove(sess Session) {
	s.mux.Lock()
	defer s.mux.Unlock()

	var err error
	for i := 0; i < s.retries; i++ {
		if err = memcache.Delete(s.ctx, s.keyPrefix+sess.ID()); err == nil || err == memcache.ErrCacheMiss {
			log.Infof(s.ctx, "Session removed: %s", sess.ID())
			delete(s.sessions, sess.ID())
			if !s.onlyMemcache {
				// Also from the Datastore:
				key := datastore.NewKey(s.ctx, s.dsEntityName, sess.ID(), 0, nil)
				datastore.Delete(s.ctx, key) // Omitting error check...
			}
			return
		}
	}
	log.Errorf(s.ctx, "Failed to remove session from memcache, id: %s, error: %v", sess.ID(), err)
}

// Close is to implement Store.Close().
func (s *memcacheStore) Close() {
	// Flush out sessions that were accessed from this store. No need locking, we're closing...
	// We could use Cocec.SetMulti(), but sessions will contain at most 1 session like all the times.
	for _, sess := range s.sessions {
		s.setMemcacheSession(sess)
	}

	if s.onlyMemcache {
		return // Don't save to Datastore
	}

	if s.asyncDatastoreSave {
		go s.saveToDatastore()
	} else {
		s.saveToDatastore()
	}
}

// saveToDatastore saves the sessions of the Store to the Datastore
// in the caller's goroutine.
func (s *memcacheStore) saveToDatastore() {
	// Save sessions that were accessed from this store. No need locking, we're closing...
	// We could use datastore.PutMulti(), but sessions will contain at most 1 session like all the times.
	for _, sess := range s.sessions {
		value, err := s.codec.Marshal(sess)
		if err != nil {
			log.Errorf(s.ctx, "Failed to marshal session: %s, error: %v", sess.ID(), err)
			continue
		}
		e := SessEntity{
			Expires: sess.Accessed().Add(sess.Timeout()),
			Value:   value,
		}
		key := datastore.NewKey(s.ctx, s.dsEntityName, sess.ID(), 0, nil)
		for i := 0; i < s.retries; i++ {
			if _, err = datastore.Put(s.ctx, key, &e); err == nil {
				break
			}
		}
		if err != nil {
			log.Errorf(s.ctx, "Failed to save session to datastore: %s, error: %v", sess.ID(), err)
		}
	}
}

// PurgeExpiredSessFromDSFunc returns a request handler function which deletes expired sessions
// from the Datastore.
// dsEntityName is the name of the entity used for saving sessions; pass an empty string
// to use the default value (which is "sess_").
//
// It is recommended to register the returned handler function to a path which then can be defined
// as a cron job to be called periodically, e.g. in every 30 minutes or so (your choice).
// As cron handlers may run up to 10 minutes, the returned handler will stop at 8 minutes
// to complete safely even if there are more expired, undeleted sessions.
//
// The response of the handler func is a JSON text telling if the handler was able to delete all expired sessions,
// or that it was finished early due to the time. Examle of a respone where all expired sessions were deleted:
//
//     {"completed":true}
func PurgeExpiredSessFromDSFunc(dsEntityName string) http.HandlerFunc {
	if dsEntityName == "" {
		dsEntityName = defaultDSEntityName
	}

	return func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)
		// Delete in batches of 100
		q := datastore.NewQuery(dsEntityName).Filter("exp<", time.Now()).KeysOnly().Limit(100)

		deadline := time.Now().Add(time.Minute * 8)

		for {
			var err error
			var keys []*datastore.Key

			if keys, err = q.GetAll(c, nil); err != nil {
				// Datastore error.
				log.Errorf(c, "Failed to query expired sessions: %v", err)
				http.Error(w, "Failed to query expired sessions!", http.StatusInternalServerError)
			}
			if len(keys) == 0 {
				// We're done, no more expired sessions
				w.Header().Set("Content-Type", "application/json")
				w.Write([]byte(`{"completed":true}`))
				return
			}

			if err = datastore.DeleteMulti(c, keys); err != nil {
				log.Errorf(c, "Error while deleting expired sessions: %v", err)
			}

			if time.Now().After(deadline) {
				// Our time is up, return
				w.Header().Set("Content-Type", "application/json")
				w.Write([]byte(`{"completed":false}`))
				return
			}
			// We have time to continue
		}
	}
}
<|endoftext|>


<commit_before>package tview

import (
	"fmt"
	"os"
	"sync"

	"github.com/gdamore/tcell"
)

// Application represents the top node of an application.
//
// It is not strictly required to use this class as none of the other classes
// depend on it. However, it provides useful tools to set up an application and
// plays nicely with all widgets.
type Application struct {
	sync.RWMutex

	// The application's screen.
	screen tcell.Screen

	// The primitive which currently has the keyboard focus.
	focus Primitive

	// The root primitive to be seen on the screen.
	root Primitive

	// Whether or not the application resizes the root primitive.
	rootFullscreen bool

	// An optional capture function which receives a key event and returns the
	// event to be forwarded to the default input handler (nil if nothing should
	// be forwarded).
	inputCapture func(event *tcell.EventKey) *tcell.EventKey

	// An optional callback function which is invoked just before the root
	// primitive is drawn.
	beforeDraw func(screen tcell.Screen) bool

	// An optional callback function which is invoked after the root primitive
	// was drawn.
	afterDraw func(screen tcell.Screen)

	// Halts the event loop during suspended mode.
	suspendMutex sync.Mutex
}

// NewApplication creates and returns a new application.
func NewApplication() *Application {
	return &Application{}
}

// SetInputCapture sets a function which captures all key events before they are
// forwarded to the key event handler of the primitive which currently has
// focus. This function can then choose to forward that key event (or a
// different one) by returning it or stop the key event processing by returning
// nil.
//
// Note that this also affects the default event handling of the application
// itself: Such a handler can intercept the Ctrl-C event which closes the
// applicatoon.
func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
	a.inputCapture = capture
	return a
}

// GetInputCapture returns the function installed with SetInputCapture() or nil
// if no such function has been installed.
func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
	return a.inputCapture
}

// Run starts the application and thus the event loop. This function returns
// when Stop() was called.
func (a *Application) Run() error {
	var err error
	a.Lock()

	// Make a screen.
	a.screen, err = tcell.NewScreen()
	if err != nil {
		a.Unlock()
		return err
	}
	if err = a.screen.Init(); err != nil {
		a.Unlock()
		return err
	}

	// We catch panics to clean up because they mess up the terminal.
	defer func() {
		if p := recover(); p != nil {
			if a.screen != nil {
				a.screen.Fini()
			}
			panic(p)
		}
	}()

	// Draw the screen for the first time.
	a.Unlock()
	a.Draw()

	// Start event loop.
	for {
		// Do not poll events during suspend mode
		a.suspendMutex.Lock()
		a.RLock()
		screen := a.screen
		a.RUnlock()
		if screen == nil {
			a.suspendMutex.Unlock()
			break
		}

		// Wait for next event.
		event := a.screen.PollEvent()
		a.suspendMutex.Unlock()
		if event == nil {
			// The screen was finalized. Exit the loop.
			break
		}

		switch event := event.(type) {
		case *tcell.EventKey:
			a.RLock()
			p := a.focus
			a.RUnlock()

			// Intercept keys.
			if a.inputCapture != nil {
				event = a.inputCapture(event)
				if event == nil {
					break // Don't forward event.
				}
			}

			// Ctrl-C closes the application.
			if event.Key() == tcell.KeyCtrlC {
				a.Stop()
			}

			// Pass other key events to the currently focused primitive.
			if p != nil {
				if handler := p.InputHandler(); handler != nil {
					handler(event, func(p Primitive) {
						a.SetFocus(p)
					})
					a.Draw()
				}
			}
		case *tcell.EventResize:
			a.RLock()
			screen := a.screen
			a.RUnlock()
			screen.Clear()
			a.Draw()
		}
	}

	return nil
}

// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
	a.Lock()
	defer a.Unlock()
	if a.screen == nil {
		return
	}
	a.screen.Fini()
	a.screen = nil
}

// Suspend temporarily suspends the application by exiting terminal UI mode and
// invoking the provided function "f". When "f" returns, terminal UI mode is
// entered again and the application resumes.
//
// A return value of true indicates that the application was suspended and "f"
// was called. If false is returned, the application was already suspended,
// terminal UI mode was not exited, and "f" was not called.
func (a *Application) Suspend(f func()) bool {
	a.RLock()

	if a.screen == nil {
		// Screen has not yet been initialized.
		a.RUnlock()
		return false
	}

	// Enter suspended mode.
	a.suspendMutex.Lock()
	defer a.suspendMutex.Unlock()
	a.RUnlock()
	a.Stop()

	// Deal with panics during suspended mode. Exit the program.
	defer func() {
		if p := recover(); p != nil {
			fmt.Println(p)
			os.Exit(1)
		}
	}()

	// Wait for "f" to return.
	f()

	// Make a new screen and redraw.
	a.Lock()
	var err error
	a.screen, err = tcell.NewScreen()
	if err != nil {
		a.Unlock()
		panic(err)
	}
	if err = a.screen.Init(); err != nil {
		a.Unlock()
		panic(err)
	}
	a.Unlock()
	a.Draw()

	// Continue application loop.
	return true
}

// Draw refreshes the screen. It calls the Draw() function of the application's
// root primitive and then syncs the screen buffer.
func (a *Application) Draw() *Application {
	a.RLock()
	screen := a.screen
	root := a.root
	fullscreen := a.rootFullscreen
	before := a.beforeDraw
	after := a.afterDraw
	a.RUnlock()

	// Maybe we're not ready yet or not anymore.
	if screen == nil || root == nil {
		return a
	}

	// Resize if requested.
	if fullscreen && root != nil {
		width, height := screen.Size()
		root.SetRect(0, 0, width, height)
	}

	// Call before handler if there is one.
	if before != nil {
		if before(screen) {
			screen.Show()
			return a
		}
	}

	// Draw all primitives.
	root.Draw(screen)

	// Call after handler if there is one.
	if after != nil {
		after(screen)
	}

	// Sync screen.
	screen.Show()

	return a
}

// SetBeforeDrawFunc installs a callback function which is invoked just before
// the root primitive is drawn during screen updates. If the function returns
// true, drawing will not continue, i.e. the root primitive will not be drawn
// (and an after-draw-handler will not be called).
//
// Note that the screen is not cleared by the application. To clear the screen,
// you may call screen.Clear().
//
// Provide nil to uninstall the callback function.
func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) *Application {
	a.beforeDraw = handler
	return a
}

// GetBeforeDrawFunc returns the callback function installed with
// SetBeforeDrawFunc() or nil if none has been installed.
func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
	return a.beforeDraw
}

// SetAfterDrawFunc installs a callback function which is invoked after the root
// primitive was drawn during screen updates.
//
// Provide nil to uninstall the callback function.
func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Application {
	a.afterDraw = handler
	return a
}

// GetAfterDrawFunc returns the callback function installed with
// SetAfterDrawFunc() or nil if none has been installed.
func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
	return a.afterDraw
}

// SetRoot sets the root primitive for this application. If "fullscreen" is set
// to true, the root primitive's position will be changed to fill the screen.
//
// This function must be called at least once or nothing will be displayed when
// the application starts.
//
// It also calls SetFocus() on the primitive.
func (a *Application) SetRoot(root Primitive, fullscreen bool) *Application {
	a.Lock()
	a.root = root
	a.rootFullscreen = fullscreen
	if a.screen != nil {
		a.screen.Clear()
	}
	a.Unlock()

	a.SetFocus(root)

	return a
}

// ResizeToFullScreen resizes the given primitive such that it fills the entire
// screen.
func (a *Application) ResizeToFullScreen(p Primitive) *Application {
	a.RLock()
	width, height := a.screen.Size()
	a.RUnlock()
	p.SetRect(0, 0, width, height)
	return a
}

// SetFocus sets the focus on a new primitive. All key events will be redirected
// to that primitive. Callers must ensure that the primitive will handle key
// events.
//
// Blur() will be called on the previously focused primitive. Focus() will be
// called on the new primitive.
func (a *Application) SetFocus(p Primitive) *Application {
	a.Lock()
	if a.focus != nil {
		a.focus.Blur()
	}
	a.focus = p
	if a.screen != nil {
		a.screen.HideCursor()
	}
	a.Unlock()
	if p != nil {
		p.Focus(func(p Primitive) {
			a.SetFocus(p)
		})
	}

	return a
}

// GetFocus returns the primitive which has the current focus. If none has it,
// nil is returned.
func (a *Application) GetFocus() Primitive {
	a.RLock()
	defer a.RUnlock()
	return a.focus
}
<commit_msg>Made Application.Draw() thread-safe (for real this time). Fixes #136<commit_after>package tview

import (
	"fmt"
	"os"
	"sync"

	"github.com/gdamore/tcell"
)

// Application represents the top node of an application.
//
// It is not strictly required to use this class as none of the other classes
// depend on it. However, it provides useful tools to set up an application and
// plays nicely with all widgets.
type Application struct {
	sync.RWMutex

	// The application's screen.
	screen tcell.Screen

	// The primitive which currently has the keyboard focus.
	focus Primitive

	// The root primitive to be seen on the screen.
	root Primitive

	// Whether or not the application resizes the root primitive.
	rootFullscreen bool

	// An optional capture function which receives a key event and returns the
	// event to be forwarded to the default input handler (nil if nothing should
	// be forwarded).
	inputCapture func(event *tcell.EventKey) *tcell.EventKey

	// An optional callback function which is invoked just before the root
	// primitive is drawn.
	beforeDraw func(screen tcell.Screen) bool

	// An optional callback function which is invoked after the root primitive
	// was drawn.
	afterDraw func(screen tcell.Screen)

	// Halts the event loop during suspended mode.
	suspendMutex sync.Mutex
}

// NewApplication creates and returns a new application.
func NewApplication() *Application {
	return &Application{}
}

// SetInputCapture sets a function which captures all key events before they are
// forwarded to the key event handler of the primitive which currently has
// focus. This function can then choose to forward that key event (or a
// different one) by returning it or stop the key event processing by returning
// nil.
//
// Note that this also affects the default event handling of the application
// itself: Such a handler can intercept the Ctrl-C event which closes the
// applicatoon.
func (a *Application) SetInputCapture(capture func(event *tcell.EventKey) *tcell.EventKey) *Application {
	a.inputCapture = capture
	return a
}

// GetInputCapture returns the function installed with SetInputCapture() or nil
// if no such function has been installed.
func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
	return a.inputCapture
}

// Run starts the application and thus the event loop. This function returns
// when Stop() was called.
func (a *Application) Run() error {
	var err error
	a.Lock()

	// Make a screen.
	a.screen, err = tcell.NewScreen()
	if err != nil {
		a.Unlock()
		return err
	}
	if err = a.screen.Init(); err != nil {
		a.Unlock()
		return err
	}

	// We catch panics to clean up because they mess up the terminal.
	defer func() {
		if p := recover(); p != nil {
			if a.screen != nil {
				a.screen.Fini()
			}
			panic(p)
		}
	}()

	// Draw the screen for the first time.
	a.Unlock()
	a.Draw()

	// Start event loop.
	for {
		// Do not poll events during suspend mode
		a.suspendMutex.Lock()
		a.RLock()
		screen := a.screen
		a.RUnlock()
		if screen == nil {
			a.suspendMutex.Unlock()
			break
		}

		// Wait for next event.
		event := a.screen.PollEvent()
		a.suspendMutex.Unlock()
		if event == nil {
			// The screen was finalized. Exit the loop.
			break
		}

		switch event := event.(type) {
		case *tcell.EventKey:
			a.RLock()
			p := a.focus
			a.RUnlock()

			// Intercept keys.
			if a.inputCapture != nil {
				event = a.inputCapture(event)
				if event == nil {
					break // Don't forward event.
				}
			}

			// Ctrl-C closes the application.
			if event.Key() == tcell.KeyCtrlC {
				a.Stop()
			}

			// Pass other key events to the currently focused primitive.
			if p != nil {
				if handler := p.InputHandler(); handler != nil {
					handler(event, func(p Primitive) {
						a.SetFocus(p)
					})
					a.Draw()
				}
			}
		case *tcell.EventResize:
			a.RLock()
			screen := a.screen
			a.RUnlock()
			screen.Clear()
			a.Draw()
		}
	}

	return nil
}

// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
	a.Lock()
	defer a.Unlock()
	if a.screen == nil {
		return
	}
	a.screen.Fini()
	a.screen = nil
}

// Suspend temporarily suspends the application by exiting terminal UI mode and
// invoking the provided function "f". When "f" returns, terminal UI mode is
// entered again and the application resumes.
//
// A return value of true indicates that the application was suspended and "f"
// was called. If false is returned, the application was already suspended,
// terminal UI mode was not exited, and "f" was not called.
func (a *Application) Suspend(f func()) bool {
	a.RLock()

	if a.screen == nil {
		// Screen has not yet been initialized.
		a.RUnlock()
		return false
	}

	// Enter suspended mode.
	a.suspendMutex.Lock()
	defer a.suspendMutex.Unlock()
	a.RUnlock()
	a.Stop()

	// Deal with panics during suspended mode. Exit the program.
	defer func() {
		if p := recover(); p != nil {
			fmt.Println(p)
			os.Exit(1)
		}
	}()

	// Wait for "f" to return.
	f()

	// Make a new screen and redraw.
	a.Lock()
	var err error
	a.screen, err = tcell.NewScreen()
	if err != nil {
		a.Unlock()
		panic(err)
	}
	if err = a.screen.Init(); err != nil {
		a.Unlock()
		panic(err)
	}
	a.Unlock()
	a.Draw()

	// Continue application loop.
	return true
}

// Draw refreshes the screen. It calls the Draw() function of the application's
// root primitive and then syncs the screen buffer.
func (a *Application) Draw() *Application {
	a.Lock()
	defer a.Unlock()

	screen := a.screen
	root := a.root
	fullscreen := a.rootFullscreen
	before := a.beforeDraw
	after := a.afterDraw

	// Maybe we're not ready yet or not anymore.
	if screen == nil || root == nil {
		return a
	}

	// Resize if requested.
	if fullscreen && root != nil {
		width, height := screen.Size()
		root.SetRect(0, 0, width, height)
	}

	// Call before handler if there is one.
	if before != nil {
		if before(screen) {
			screen.Show()
			return a
		}
	}

	// Draw all primitives.
	root.Draw(screen)

	// Call after handler if there is one.
	if after != nil {
		after(screen)
	}

	// Sync screen.
	screen.Show()

	return a
}

// SetBeforeDrawFunc installs a callback function which is invoked just before
// the root primitive is drawn during screen updates. If the function returns
// true, drawing will not continue, i.e. the root primitive will not be drawn
// (and an after-draw-handler will not be called).
//
// Note that the screen is not cleared by the application. To clear the screen,
// you may call screen.Clear().
//
// Provide nil to uninstall the callback function.
func (a *Application) SetBeforeDrawFunc(handler func(screen tcell.Screen) bool) *Application {
	a.beforeDraw = handler
	return a
}

// GetBeforeDrawFunc returns the callback function installed with
// SetBeforeDrawFunc() or nil if none has been installed.
func (a *Application) GetBeforeDrawFunc() func(screen tcell.Screen) bool {
	return a.beforeDraw
}

// SetAfterDrawFunc installs a callback function which is invoked after the root
// primitive was drawn during screen updates.
//
// Provide nil to uninstall the callback function.
func (a *Application) SetAfterDrawFunc(handler func(screen tcell.Screen)) *Application {
	a.afterDraw = handler
	return a
}

// GetAfterDrawFunc returns the callback function installed with
// SetAfterDrawFunc() or nil if none has been installed.
func (a *Application) GetAfterDrawFunc() func(screen tcell.Screen) {
	return a.afterDraw
}

// SetRoot sets the root primitive for this application. If "fullscreen" is set
// to true, the root primitive's position will be changed to fill the screen.
//
// This function must be called at least once or nothing will be displayed when
// the application starts.
//
// It also calls SetFocus() on the primitive.
func (a *Application) SetRoot(root Primitive, fullscreen bool) *Application {
	a.Lock()
	a.root = root
	a.rootFullscreen = fullscreen
	if a.screen != nil {
		a.screen.Clear()
	}
	a.Unlock()

	a.SetFocus(root)

	return a
}

// ResizeToFullScreen resizes the given primitive such that it fills the entire
// screen.
func (a *Application) ResizeToFullScreen(p Primitive) *Application {
	a.RLock()
	width, height := a.screen.Size()
	a.RUnlock()
	p.SetRect(0, 0, width, height)
	return a
}

// SetFocus sets the focus on a new primitive. All key events will be redirected
// to that primitive. Callers must ensure that the primitive will handle key
// events.
//
// Blur() will be called on the previously focused primitive. Focus() will be
// called on the new primitive.
func (a *Application) SetFocus(p Primitive) *Application {
	a.Lock()
	if a.focus != nil {
		a.focus.Blur()
	}
	a.focus = p
	if a.screen != nil {
		a.screen.HideCursor()
	}
	a.Unlock()
	if p != nil {
		p.Focus(func(p Primitive) {
			a.SetFocus(p)
		})
	}

	return a
}

// GetFocus returns the primitive which has the current focus. If none has it,
// nil is returned.
func (a *Application) GetFocus() Primitive {
	a.RLock()
	defer a.RUnlock()
	return a.focus
}
<|endoftext|>


<commit_before>package scanner

import (
	"bytes"
	"io"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

// Ensures that a positive number can be scanned.
func TestScanPositiveNumber(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader("100")).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)
	assert.Equal(t, string(b), "100")
}

// Ensures that a negative number can be scanned.
func TestScanNegativeNumber(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader("-1")).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)
	assert.Equal(t, string(b), "-1")
}

// Ensures that a fractional number can be scanned.
func TestScanFloat(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader("120.12931")).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)
	assert.Equal(t, string(b), "120.12931")
}

// Ensures that a quoted string can be scanned.
func TestScanString(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader(`"hello world"`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TSTRING)
	assert.Equal(t, string(b), "hello world")
}

// Ensures that a quoted string with escaped characters can be scanned.
func TestScanEscapedString(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader(`"\"\\\/\b\f\n\r\t"`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TSTRING)
	assert.Equal(t, string(b), "\"\\/\b\f\n\r\t")
}

// Ensures that escaped unicode sequences can be decoded.
func TestScanEscapedUnicode(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader(`"\u0026 \u0424 \u03B4 \u03b4"`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TSTRING)
	assert.Equal(t, string(b), "& Ф δ δ")
}

// Ensures that a true value can be scanned.
func TestScanTrue(t *testing.T) {
	tok, _, err := NewScanner(strings.NewReader(`true`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TTRUE)
}

// Ensures that a false value can be scanned.
func TestScanFalse(t *testing.T) {
	tok, _, err := NewScanner(strings.NewReader(`false`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TFALSE)
}

// Ensures that a null value can be scanned.
func TestScanNull(t *testing.T) {
	tok, _, err := NewScanner(strings.NewReader(`null`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNULL)
}

// Ensures that an EOF gets returned.
func TestScanEOF(t *testing.T) {
	_, _, err := NewScanner(strings.NewReader(``)).Scan()
	assert.Equal(t, err, io.EOF)
}

// Ensures that a string can be read into a field.
func TestReadString(t *testing.T) {
	var v string
	err := NewScanner(strings.NewReader(`"foo"`)).ReadString(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, "foo")
}

// Ensures that strings largers than allocated buffer can be read.
func TestReadHugeString(t *testing.T) {
	var v string
	huge := strings.Repeat("s", bufSize*2)
	err := NewScanner(strings.NewReader(`"` + huge + `"`)).ReadString(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, huge)
}

// Ensures that a non-string value is read into a string field as blank.
func TestReadNonStringAsString(t *testing.T) {
	var v string
	err := NewScanner(strings.NewReader(`12`)).ReadString(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, "")
}

// Ensures that a non-value returns a read error.
func TestReadNonValueAsString(t *testing.T) {
	var v string
	err := NewScanner(strings.NewReader(`{`)).ReadString(&v)
	assert.Error(t, err)
}

// Ensures that an int can be read into a field.
func TestReadInt(t *testing.T) {
	var v int
	err := NewScanner(strings.NewReader(`100`)).ReadInt(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, 100)
}

// Ensures that a non-number value is read into an int field as zero.
func TestReadNonNumberAsInt(t *testing.T) {
	var v int
	err := NewScanner(strings.NewReader(`"foo"`)).ReadInt(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, 0)
}

// Ensures that an int64 can be read into a field.
func TestReadInt64(t *testing.T) {
	var v int64
	err := NewScanner(strings.NewReader(`-100`)).ReadInt64(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, -100)
}

// Ensures that a uint can be read into a field.
func TestReadUint(t *testing.T) {
	var v uint
	err := NewScanner(strings.NewReader(`100`)).ReadUint(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, uint(100))
}

// Ensures that an uint64 can be read into a field.
func TestReadUint64(t *testing.T) {
	var v uint64
	err := NewScanner(strings.NewReader(`1024`)).ReadUint64(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, uint(1024))
}

// Ensures that a float32 can be read into a field.
func TestReadFloat32(t *testing.T) {
	var v float32
	err := NewScanner(strings.NewReader(`1293.123`)).ReadFloat32(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, float32(1293.123))
}

// Ensures that a float64 can be read into a field.
func TestReadFloat64(t *testing.T) {
	var v float64
	err := NewScanner(strings.NewReader(`9871293.414123`)).ReadFloat64(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, 9871293.414123)
}

// Ensures that a boolean can be read into a field.
func TestReadBoolTrue(t *testing.T) {
	var v bool
	err := NewScanner(strings.NewReader(`true`)).ReadBool(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, true)
}

// Ensures whitespace between tokens are ignored.
func TestScanIgnoreWhitespace(t *testing.T) {
	s := NewScanner(strings.NewReader(" 100 true false "))

	tok, _, err := s.Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)

	tok, _, err = s.Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TTRUE)

	tok, _, err = s.Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TFALSE)

	tok, _, err = s.Scan()
	assert.Equal(t, err, io.EOF)
	assert.Equal(t, tok, 0)
}

// Ensures that a map can be read into a field.
func TestReadMap(t *testing.T) {
	var v map[string]interface{}
	err := NewScanner(strings.NewReader(`{"foo":"bar", "bat":1293,"truex":true,"falsex":false,"nullx":null,"nested":{"xxx":"yyy"}}`)).ReadMap(&v)
	assert.NoError(t, err)
	assert.Equal(t, v["foo"], "bar")
	assert.Equal(t, v["bat"], float64(1293))
	assert.Equal(t, v["truex"], true)
	assert.Equal(t, v["falsex"], false)
	_, exists := v["nullx"]
	assert.Equal(t, v["nullx"], nil)
	assert.True(t, exists)
	assert.NotNil(t, v["nested"])
	nested := v["nested"].(map[string]interface{})
	assert.Equal(t, nested["xxx"], "yyy")
}

func BenchmarkScanNumber(b *testing.B) {
	withBuffer(b, "100", func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkScanString(b *testing.B) {
	withBuffer(b, `"01234567"`, func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkScanLongString(b *testing.B) {
	withBuffer(b, `"foo foo foo foo foo foo foo foo foo foo foo foo foo foo"`, func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkScanEscapedString(b *testing.B) {
	withBuffer(b, `"\"\\\/\b\f\n\r\t"`, func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadString(b *testing.B) {
	withBuffer(b, `"01234567"`, func(buf []byte) {
		var v string
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadString(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadLongString(b *testing.B) {
	withBuffer(b, `"foo foo foo foo foo foo foo foo foo foo foo foo foo foo"`, func(buf []byte) {
		var v string
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadString(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadInt(b *testing.B) {
	withBuffer(b, `"100"`, func(buf []byte) {
		var v int
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadInt(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadFloat64(b *testing.B) {
	withBuffer(b, `"9871293.414123"`, func(buf []byte) {
		var v float64
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadFloat64(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadBool(b *testing.B) {
	withBuffer(b, `true`, func(buf []byte) {
		var v bool
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadBool(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func withBuffer(b *testing.B, value string, fn func([]byte)) {
	b.StopTimer()
	var str string
	for i := 0; i < 1000; i++ {
		str += value + " "
	}
	b.StartTimer()

	fn([]byte(str))

	b.SetBytes(int64(len(value)))
}
<commit_msg>Ensure we support overflows more than once.<commit_after>package scanner

import (
	"bytes"
	"io"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

// Ensures that a positive number can be scanned.
func TestScanPositiveNumber(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader("100")).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)
	assert.Equal(t, string(b), "100")
}

// Ensures that a negative number can be scanned.
func TestScanNegativeNumber(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader("-1")).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)
	assert.Equal(t, string(b), "-1")
}

// Ensures that a fractional number can be scanned.
func TestScanFloat(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader("120.12931")).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)
	assert.Equal(t, string(b), "120.12931")
}

// Ensures that a quoted string can be scanned.
func TestScanString(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader(`"hello world"`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TSTRING)
	assert.Equal(t, string(b), "hello world")
}

// Ensures that a quoted string with escaped characters can be scanned.
func TestScanEscapedString(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader(`"\"\\\/\b\f\n\r\t"`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TSTRING)
	assert.Equal(t, string(b), "\"\\/\b\f\n\r\t")
}

// Ensures that escaped unicode sequences can be decoded.
func TestScanEscapedUnicode(t *testing.T) {
	tok, b, err := NewScanner(strings.NewReader(`"\u0026 \u0424 \u03B4 \u03b4"`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TSTRING)
	assert.Equal(t, string(b), "& Ф δ δ")
}

// Ensures that a true value can be scanned.
func TestScanTrue(t *testing.T) {
	tok, _, err := NewScanner(strings.NewReader(`true`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TTRUE)
}

// Ensures that a false value can be scanned.
func TestScanFalse(t *testing.T) {
	tok, _, err := NewScanner(strings.NewReader(`false`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TFALSE)
}

// Ensures that a null value can be scanned.
func TestScanNull(t *testing.T) {
	tok, _, err := NewScanner(strings.NewReader(`null`)).Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNULL)
}

// Ensures that an EOF gets returned.
func TestScanEOF(t *testing.T) {
	_, _, err := NewScanner(strings.NewReader(``)).Scan()
	assert.Equal(t, err, io.EOF)
}

// Ensures that a string can be read into a field.
func TestReadString(t *testing.T) {
	var v string
	err := NewScanner(strings.NewReader(`"foo"`)).ReadString(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, "foo")
}

// Ensures that strings largers than allocated buffer can be read.
func TestReadHugeString(t *testing.T) {
	var v string
	huge := strings.Repeat("s", bufSize*3)
	err := NewScanner(strings.NewReader(`"` + huge + `"`)).ReadString(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, huge)
}

// Ensures that a non-string value is read into a string field as blank.
func TestReadNonStringAsString(t *testing.T) {
	var v string
	err := NewScanner(strings.NewReader(`12`)).ReadString(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, "")
}

// Ensures that a non-value returns a read error.
func TestReadNonValueAsString(t *testing.T) {
	var v string
	err := NewScanner(strings.NewReader(`{`)).ReadString(&v)
	assert.Error(t, err)
}

// Ensures that an int can be read into a field.
func TestReadInt(t *testing.T) {
	var v int
	err := NewScanner(strings.NewReader(`100`)).ReadInt(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, 100)
}

// Ensures that a non-number value is read into an int field as zero.
func TestReadNonNumberAsInt(t *testing.T) {
	var v int
	err := NewScanner(strings.NewReader(`"foo"`)).ReadInt(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, 0)
}

// Ensures that an int64 can be read into a field.
func TestReadInt64(t *testing.T) {
	var v int64
	err := NewScanner(strings.NewReader(`-100`)).ReadInt64(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, -100)
}

// Ensures that a uint can be read into a field.
func TestReadUint(t *testing.T) {
	var v uint
	err := NewScanner(strings.NewReader(`100`)).ReadUint(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, uint(100))
}

// Ensures that an uint64 can be read into a field.
func TestReadUint64(t *testing.T) {
	var v uint64
	err := NewScanner(strings.NewReader(`1024`)).ReadUint64(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, uint(1024))
}

// Ensures that a float32 can be read into a field.
func TestReadFloat32(t *testing.T) {
	var v float32
	err := NewScanner(strings.NewReader(`1293.123`)).ReadFloat32(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, float32(1293.123))
}

// Ensures that a float64 can be read into a field.
func TestReadFloat64(t *testing.T) {
	var v float64
	err := NewScanner(strings.NewReader(`9871293.414123`)).ReadFloat64(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, 9871293.414123)
}

// Ensures that a boolean can be read into a field.
func TestReadBoolTrue(t *testing.T) {
	var v bool
	err := NewScanner(strings.NewReader(`true`)).ReadBool(&v)
	assert.NoError(t, err)
	assert.Equal(t, v, true)
}

// Ensures whitespace between tokens are ignored.
func TestScanIgnoreWhitespace(t *testing.T) {
	s := NewScanner(strings.NewReader(" 100 true false "))

	tok, _, err := s.Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TNUMBER)

	tok, _, err = s.Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TTRUE)

	tok, _, err = s.Scan()
	assert.NoError(t, err)
	assert.Equal(t, tok, TFALSE)

	tok, _, err = s.Scan()
	assert.Equal(t, err, io.EOF)
	assert.Equal(t, tok, 0)
}

// Ensures that a map can be read into a field.
func TestReadMap(t *testing.T) {
	var v map[string]interface{}
	err := NewScanner(strings.NewReader(`{"foo":"bar", "bat":1293,"truex":true,"falsex":false,"nullx":null,"nested":{"xxx":"yyy"}}`)).ReadMap(&v)
	assert.NoError(t, err)
	assert.Equal(t, v["foo"], "bar")
	assert.Equal(t, v["bat"], float64(1293))
	assert.Equal(t, v["truex"], true)
	assert.Equal(t, v["falsex"], false)
	_, exists := v["nullx"]
	assert.Equal(t, v["nullx"], nil)
	assert.True(t, exists)
	assert.NotNil(t, v["nested"])
	nested := v["nested"].(map[string]interface{})
	assert.Equal(t, nested["xxx"], "yyy")
}

func BenchmarkScanNumber(b *testing.B) {
	withBuffer(b, "100", func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkScanString(b *testing.B) {
	withBuffer(b, `"01234567"`, func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkScanLongString(b *testing.B) {
	withBuffer(b, `"foo foo foo foo foo foo foo foo foo foo foo foo foo foo"`, func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkScanEscapedString(b *testing.B) {
	withBuffer(b, `"\"\\\/\b\f\n\r\t"`, func(buf []byte) {
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if _, _, err := s.Scan(); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadString(b *testing.B) {
	withBuffer(b, `"01234567"`, func(buf []byte) {
		var v string
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadString(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadLongString(b *testing.B) {
	withBuffer(b, `"foo foo foo foo foo foo foo foo foo foo foo foo foo foo"`, func(buf []byte) {
		var v string
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadString(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadInt(b *testing.B) {
	withBuffer(b, `"100"`, func(buf []byte) {
		var v int
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadInt(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadFloat64(b *testing.B) {
	withBuffer(b, `"9871293.414123"`, func(buf []byte) {
		var v float64
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadFloat64(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func BenchmarkReadBool(b *testing.B) {
	withBuffer(b, `true`, func(buf []byte) {
		var v bool
		s := NewScanner(bytes.NewBuffer(buf))
		for i := 0; i < b.N; i++ {
			if err := s.ReadBool(&v); err == io.EOF {
				s = NewScanner(bytes.NewBuffer(buf))
			} else if err != nil {
				b.Fatal("scan error:", err)
			}
		}
	})
}

func withBuffer(b *testing.B, value string, fn func([]byte)) {
	b.StopTimer()
	var str string
	for i := 0; i < 1000; i++ {
		str += value + " "
	}
	b.StartTimer()

	fn([]byte(str))

	b.SetBytes(int64(len(value)))
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/jingweno/travisarchive/db"
	"github.com/jingweno/travisarchive/export/uploader"
	"github.com/joho/godotenv"
)

var (
	execDir  string
	mongoURL string
)

func init() {
	err := godotenv.Load("../.env")
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	flag.StringVar(&execDir, "e", "", "dir to the mongoexport executable")
	flag.StringVar(&mongoURL, "u", os.Getenv("MONGOHQ_URL"), "URL of the Mongo server")
}

func main() {
	flag.Parse()

	if execDir == "" {
		log.Fatal(fmt.Errorf("specify the dir to the mongoexport executable with -e"))
	}

	if mongoURL == "" {
		log.Fatal(fmt.Errorf("specify the URL of Mongo server with -u"))
	}

	db, err := db.Connect()
	if err != nil {
		log.Fatal(err)
	}

	cols, err := db.DB().CollectionNames()
	if err != nil {
		log.Fatal(err)
	}

	exportBuilds(cols)
}

func exportBuilds(cols []string) {
	oneDayAgo := time.Now().Add(-24 * time.Hour).UTC()
	for _, col := range cols {
		d, err := parseDate(col)
		if err != nil {
			continue
		}

		if d.UTC().After(oneDayAgo) {
			continue
		}

		log.Printf("exporting %s...\n", col)
		cmd := &MongoExport{ExecDir: execDir, URL: mongoURL, ColName: col}
		outfile, err := cmd.Run()
		if err != nil {
			log.Println(err)
			continue
		}

		log.Printf("exported to %s\n", outfile)

		archiver := &Archiver{outfile}
		outzip, err := archiver.Archive()
		if err != nil {
			log.Println(err)
			continue
		}

		log.Printf("archived to %s\n", outzip)

		zipfile, err := os.Open(outzip)
		if err != nil {
			log.Println(err)
			continue
		}
		defer zipfile.Close()

		filename := fmt.Sprintf("/builds/%s", filepath.Base(outzip))
		u, err := uploader.New("s3")
		if err != nil {
			log.Println(err)
			continue
		}
		err = u.Upload(filename, "application/zip", zipfile)
		if err != nil {
			log.Println(err)
			continue
		}

		log.Printf("uploaded to s3 %s", filename)
	}
}

func parseDate(col string) (time.Time, error) {
	if !strings.HasPrefix(col, "builds_") {
		return time.Time{}, fmt.Errorf("input doesn't include the right prefix")
	}

	timePart := strings.SplitN(col, "_", 2)[1]
	form := "2006_01_02"

	return time.Parse(form, timePart)
}
<commit_msg>Mute .env load error<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/jingweno/travisarchive/db"
	"github.com/jingweno/travisarchive/export/uploader"
	"github.com/joho/godotenv"
)

var (
	execDir  string
	mongoURL string
)

func init() {
	godotenv.Load("../.env")
	flag.StringVar(&execDir, "e", "", "dir to the mongoexport executable")
	flag.StringVar(&mongoURL, "u", os.Getenv("MONGOHQ_URL"), "URL of the Mongo server")
}

func main() {
	flag.Parse()

	if execDir == "" {
		log.Fatal(fmt.Errorf("specify the dir to the mongoexport executable with -e"))
	}

	if mongoURL == "" {
		log.Fatal(fmt.Errorf("specify the URL of Mongo server with -u"))
	}

	db, err := db.Connect()
	if err != nil {
		log.Fatal(err)
	}

	cols, err := db.DB().CollectionNames()
	if err != nil {
		log.Fatal(err)
	}

	exportBuilds(cols)
}

func exportBuilds(cols []string) {
	oneDayAgo := time.Now().Add(-24 * time.Hour).UTC()
	for _, col := range cols {
		d, err := parseDate(col)
		if err != nil {
			continue
		}

		if d.UTC().After(oneDayAgo) {
			continue
		}

		log.Printf("exporting %s...\n", col)
		cmd := &MongoExport{ExecDir: execDir, URL: mongoURL, ColName: col}
		outfile, err := cmd.Run()
		if err != nil {
			log.Println(err)
			continue
		}

		log.Printf("exported to %s\n", outfile)

		archiver := &Archiver{outfile}
		outzip, err := archiver.Archive()
		if err != nil {
			log.Println(err)
			continue
		}

		log.Printf("archived to %s\n", outzip)

		zipfile, err := os.Open(outzip)
		if err != nil {
			log.Println(err)
			continue
		}
		defer zipfile.Close()

		filename := fmt.Sprintf("/builds/%s", filepath.Base(outzip))
		u, err := uploader.New("s3")
		if err != nil {
			log.Println(err)
			continue
		}
		err = u.Upload(filename, "application/zip", zipfile)
		if err != nil {
			log.Println(err)
			continue
		}

		log.Printf("uploaded to s3 %s", filename)
	}
}

func parseDate(col string) (time.Time, error) {
	if !strings.HasPrefix(col, "builds_") {
		return time.Time{}, fmt.Errorf("input doesn't include the right prefix")
	}

	timePart := strings.SplitN(col, "_", 2)[1]
	form := "2006_01_02"

	return time.Parse(form, timePart)
}
<|endoftext|>


<commit_before>//Author Sarah Carroll
//Date :20/09/2017

// Adapter from:https://tour.golang.org/welcome/4

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Welcome to the playground!")

	fmt.Println("The time is", time.Now())
}<commit_msg>comments added<commit_after>//Author Sarah Carroll
//Date :20/09/2017

// Adapter from:https://tour.golang.org/welcome/4 https://golang.org/pkg/time/

package main

//Package time provides functionality for measuring and displaying time.
import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Welcome to the playground!")

	fmt.Println("The time is", time.Now())
}<|endoftext|>


<commit_before>package domain_bbs

import (
	"path"

	"github.com/cloudfoundry-incubator/runtime-schema/bbs/shared"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry/storeadapter"
	"github.com/pivotal-golang/lager"
)

type DomainBBS struct {
	store  storeadapter.StoreAdapter
	logger lager.Logger
}

func New(
	store storeadapter.StoreAdapter,
	logger lager.Logger,
) *DomainBBS {
	return &DomainBBS{
		store:  store,
		logger: logger,
	}
}

func (bbs *DomainBBS) UpsertDomain(domain string, ttlInSeconds int) error {
	var validationError models.ValidationError

	if domain == "" {
		validationError = validationError.Append(models.ErrInvalidParameter{"domain"})
	}

	if ttlInSeconds < 0 {
		validationError = validationError.Append(models.ErrInvalidParameter{"ttlInSeconds"})
	}

	if !validationError.Empty() {
		return validationError
	}

	return shared.ConvertStoreError(bbs.store.SetMulti([]storeadapter.StoreNode{
		{
			Key: shared.DomainSchemaPath(domain),
			TTL: uint64(ttlInSeconds),
		},
	}))
}

func (bbs *DomainBBS) Domains() ([]string, error) {
	node, err := bbs.store.ListRecursively(shared.DomainSchemaRoot)
	if err != nil && err != storeadapter.ErrorKeyNotFound {
		return nil, shared.ConvertStoreError(err)
	}

	domains := make([]string, 0, len(node.ChildNodes))

	for _, node := range node.ChildNodes {
		domains = append(domains, path.Base(node.Key))
	}

	return domains, nil
}
<commit_msg>Add logging to UpsertDomain<commit_after>package domain_bbs

import (
	"path"

	"github.com/cloudfoundry-incubator/runtime-schema/bbs/shared"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry/storeadapter"
	"github.com/pivotal-golang/lager"
)

type DomainBBS struct {
	store  storeadapter.StoreAdapter
	logger lager.Logger
}

func New(
	store storeadapter.StoreAdapter,
	logger lager.Logger,
) *DomainBBS {
	return &DomainBBS{
		store:  store,
		logger: logger,
	}
}

func (bbs *DomainBBS) UpsertDomain(domain string, ttlInSeconds int) error {
	logger := bbs.logger.Session("upsert-domain", lager.Data{
		"domain": domain,
		"ttl":    ttlInSeconds,
	})
	defer logger.Info("finished")

	var validationError models.ValidationError

	if domain == "" {
		validationError = validationError.Append(models.ErrInvalidParameter{"domain"})
	}

	if ttlInSeconds < 0 {
		validationError = validationError.Append(models.ErrInvalidParameter{"ttlInSeconds"})
	}

	if !validationError.Empty() {
		return validationError
	}

	logger.Info("starting")
	return shared.ConvertStoreError(bbs.store.SetMulti([]storeadapter.StoreNode{
		{
			Key: shared.DomainSchemaPath(domain),
			TTL: uint64(ttlInSeconds),
		},
	}))
}

func (bbs *DomainBBS) Domains() ([]string, error) {
	node, err := bbs.store.ListRecursively(shared.DomainSchemaRoot)
	if err != nil && err != storeadapter.ErrorKeyNotFound {
		return nil, shared.ConvertStoreError(err)
	}

	domains := make([]string, 0, len(node.ChildNodes))

	for _, node := range node.ChildNodes {
		domains = append(domains, path.Base(node.Key))
	}

	return domains, nil
}
<|endoftext|>


<commit_before>/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package metrics

import (
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/hyperledger/fabric/core/config/configtest"
	. "github.com/onsi/gomega"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestStartSuccessStatsd(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Reporter: statsdReporterType,
		Interval: 1 * time.Second,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:8125",
			FlushInterval: 2 * time.Second,
			FlushBytes:    512,
		}}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)
}

func TestStartSuccessProm(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Reporter: promReporterType,
		Interval: 1 * time.Second,
		PromReporterOpts: PromReporterOpts{
			ListenAddress: "127.0.0.1:8081",
		}}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)
}

func TestStartDisabled(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled: false,
	}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)
}

func TestStartInvalidInterval(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 0,
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartStatsdInvalidAddress(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "",
			FlushInterval: 2 * time.Second,
			FlushBytes:    512,
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartStatsdInvalidFlushInterval(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:8125",
			FlushInterval: 0,
			FlushBytes:    512,
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartPromInvalidListernAddress(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		PromReporterOpts: PromReporterOpts{
			ListenAddress: "",
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartStatsdInvalidFlushBytes(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:8125",
			FlushInterval: 2 * time.Second,
			FlushBytes:    0,
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartInvalidReporter(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: "test",
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartAndClose(t *testing.T) {
	t.Parallel()
	gt := NewGomegaWithT(t)
	defer Shutdown()
	opts := Opts{
		Enabled:  true,
		Reporter: statsdReporterType,
		Interval: 1 * time.Second,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:8125",
			FlushInterval: 2 * time.Second,
			FlushBytes:    512,
		}}
	Init(opts)
	assert.NotNil(t, RootScope)
	go Start()
	gt.Eventually(isRunning).Should(BeTrue())
}

func TestNoOpScopeMetrics(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled: false,
	}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)

	// make sure no error throws when invoke noOpScope
	subScope := s.SubScope("test")
	subScope.Counter("foo").Inc(2)
	subScope.Gauge("bar").Update(1.33)
	tagSubScope := subScope.Tagged(map[string]string{"env": "test"})
	tagSubScope.Counter("foo").Inc(2)
	tagSubScope.Gauge("bar").Update(1.33)
}

func TestNewOpts(t *testing.T) {
	t.Parallel()
	defer viper.Reset()
	setupTestConfig()
	opts := NewOpts()
	assert.False(t, opts.Enabled)
	assert.Equal(t, 1*time.Second, opts.Interval)
	assert.Equal(t, statsdReporterType, opts.Reporter)
	assert.Equal(t, 1432, opts.StatsdReporterOpts.FlushBytes)
	assert.Equal(t, 2*time.Second, opts.StatsdReporterOpts.FlushInterval)
	assert.Equal(t, "0.0.0.0:8125", opts.StatsdReporterOpts.Address)
	viper.Reset()

	setupTestConfig()
	viper.Set("metrics.Reporter", promReporterType)
	opts1 := NewOpts()
	assert.False(t, opts1.Enabled)
	assert.Equal(t, 1*time.Second, opts1.Interval)
	assert.Equal(t, promReporterType, opts1.Reporter)
	assert.Equal(t, "0.0.0.0:8080", opts1.PromReporterOpts.ListenAddress)
}

func TestNewOptsDefaultVar(t *testing.T) {
	t.Parallel()
	opts := NewOpts()
	assert.False(t, opts.Enabled)
	assert.Equal(t, 1*time.Second, opts.Interval)
	assert.Equal(t, statsdReporterType, opts.Reporter)
	assert.Equal(t, 1432, opts.StatsdReporterOpts.FlushBytes)
	assert.Equal(t, 2*time.Second, opts.StatsdReporterOpts.FlushInterval)
}

func setupTestConfig() {
	viper.SetConfigName("core")
	viper.SetEnvPrefix("CORE")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	viper.AutomaticEnv()

	err := configtest.AddDevConfigPath(nil)
	if err != nil {
		panic(fmt.Errorf("Fatal error adding dev dir: %s \n", err))
	}

	err = viper.ReadInConfig()
	if err != nil { // Handle errors reading the config file
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}
}
<commit_msg>FAB-10344 Make metrics tests use ephemeral ports<commit_after>/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package metrics

import (
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/hyperledger/fabric/core/config/configtest"
	. "github.com/onsi/gomega"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestStartSuccessStatsd(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Reporter: statsdReporterType,
		Interval: 1 * time.Second,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:0",
			FlushInterval: 2 * time.Second,
			FlushBytes:    512,
		}}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)
}

func TestStartSuccessProm(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Reporter: promReporterType,
		Interval: 1 * time.Second,
		PromReporterOpts: PromReporterOpts{
			ListenAddress: "127.0.0.1:0",
		}}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)
}

func TestStartDisabled(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled: false,
	}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)
}

func TestStartInvalidInterval(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 0,
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartStatsdInvalidAddress(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "",
			FlushInterval: 2 * time.Second,
			FlushBytes:    512,
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartStatsdInvalidFlushInterval(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:0",
			FlushInterval: 0,
			FlushBytes:    512,
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartPromInvalidListernAddress(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		PromReporterOpts: PromReporterOpts{
			ListenAddress: "",
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartStatsdInvalidFlushBytes(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: statsdReporterType,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:0",
			FlushInterval: 2 * time.Second,
			FlushBytes:    0,
		},
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartInvalidReporter(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled:  true,
		Interval: 1 * time.Second,
		Reporter: "test",
	}
	s, err := create(opts)
	assert.Nil(t, s)
	assert.Error(t, err)
}

func TestStartAndClose(t *testing.T) {
	t.Parallel()
	gt := NewGomegaWithT(t)
	defer Shutdown()
	opts := Opts{
		Enabled:  true,
		Reporter: statsdReporterType,
		Interval: 1 * time.Second,
		StatsdReporterOpts: StatsdReporterOpts{
			Address:       "127.0.0.1:0",
			FlushInterval: 2 * time.Second,
			FlushBytes:    512,
		}}
	Init(opts)
	assert.NotNil(t, RootScope)
	go Start()
	gt.Eventually(isRunning).Should(BeTrue())
}

func TestNoOpScopeMetrics(t *testing.T) {
	t.Parallel()
	opts := Opts{
		Enabled: false,
	}
	s, err := create(opts)
	go s.Start()
	defer s.Close()
	assert.NotNil(t, s)
	assert.NoError(t, err)

	// make sure no error throws when invoke noOpScope
	subScope := s.SubScope("test")
	subScope.Counter("foo").Inc(2)
	subScope.Gauge("bar").Update(1.33)
	tagSubScope := subScope.Tagged(map[string]string{"env": "test"})
	tagSubScope.Counter("foo").Inc(2)
	tagSubScope.Gauge("bar").Update(1.33)
}

func TestNewOpts(t *testing.T) {
	t.Parallel()
	defer viper.Reset()
	setupTestConfig()
	opts := NewOpts()
	assert.False(t, opts.Enabled)
	assert.Equal(t, 1*time.Second, opts.Interval)
	assert.Equal(t, statsdReporterType, opts.Reporter)
	assert.Equal(t, 1432, opts.StatsdReporterOpts.FlushBytes)
	assert.Equal(t, 2*time.Second, opts.StatsdReporterOpts.FlushInterval)
	assert.Equal(t, "0.0.0.0:8125", opts.StatsdReporterOpts.Address)
	viper.Reset()

	setupTestConfig()
	viper.Set("metrics.Reporter", promReporterType)
	opts1 := NewOpts()
	assert.False(t, opts1.Enabled)
	assert.Equal(t, 1*time.Second, opts1.Interval)
	assert.Equal(t, promReporterType, opts1.Reporter)
	assert.Equal(t, "0.0.0.0:8080", opts1.PromReporterOpts.ListenAddress)
}

func TestNewOptsDefaultVar(t *testing.T) {
	t.Parallel()
	opts := NewOpts()
	assert.False(t, opts.Enabled)
	assert.Equal(t, 1*time.Second, opts.Interval)
	assert.Equal(t, statsdReporterType, opts.Reporter)
	assert.Equal(t, 1432, opts.StatsdReporterOpts.FlushBytes)
	assert.Equal(t, 2*time.Second, opts.StatsdReporterOpts.FlushInterval)
}

func setupTestConfig() {
	viper.SetConfigName("core")
	viper.SetEnvPrefix("CORE")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
	viper.AutomaticEnv()

	err := configtest.AddDevConfigPath(nil)
	if err != nil {
		panic(fmt.Errorf("Fatal error adding dev dir: %s \n", err))
	}

	err = viper.ReadInConfig()
	if err != nil { // Handle errors reading the config file
		panic(fmt.Errorf("Fatal error config file: %s \n", err))
	}
}
<|endoftext|>


<commit_before>//  Copyright (c) 2012 Couchbase, Inc.
//  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 rest

import (
	"encoding/json"
	"mime/multipart"
	"net/http"

	"github.com/couchbaselabs/sync_gateway/base"
	"github.com/couchbaselabs/sync_gateway/db"
)

// HTTP handler for a GET of a document
func (h *handler) handleGetDoc() error {
	docid := h.PathVars()["docid"]
	revid := h.getQuery("rev")
	includeRevs := h.getBoolQuery("revs")
	openRevs := h.getQuery("open_revs")

	// What attachment bodies should be included?
	var attachmentsSince []string = nil
	if h.getBoolQuery("attachments") {
		atts := h.getQuery("atts_since")
		if atts != "" {
			err := json.Unmarshal([]byte(atts), &attachmentsSince)
			if err != nil {
				return &base.HTTPError{http.StatusBadRequest, "bad atts_since"}
			}
		} else {
			attachmentsSince = []string{}
		}
	}

	if openRevs == "" {
		// Single-revision GET:
		value, err := h.db.GetRev(docid, revid, includeRevs, attachmentsSince)
		if err != nil {
			return err
		}
		if value == nil {
			return kNotFoundError
		}
		h.setHeader("Etag", value["_rev"].(string))

		hasBodies := (attachmentsSince != nil && value["_attachments"] != nil)
		if h.requestAccepts("multipart/") && (hasBodies || !h.requestAccepts("application/json")) {
			return h.writeMultipart(func(writer *multipart.Writer) error {
				h.db.WriteMultipartDocument(value, writer)
				return nil
			})
		} else {
			h.writeJSON(value)
		}

	} else if openRevs == "all" {
		// open_revs=all:
		return &base.HTTPError{http.StatusNotImplemented, "open_revs=all unimplemented"} // TODO

	} else {
		// open_revs=["id1", "id2", ...]
		attachmentsSince = []string{}
		var revids []string
		err := json.Unmarshal([]byte(openRevs), &revids)
		if err != nil {
			return &base.HTTPError{http.StatusBadRequest, "bad open_revs"}
		}

		err = h.writeMultipart(func(writer *multipart.Writer) error {
			for _, revid := range revids {
				revBody, err := h.db.GetRev(docid, revid, includeRevs, attachmentsSince)
				if err != nil {
					revBody = db.Body{"missing": revid} //TODO: More specific error
				}
				h.db.WriteRevisionAsPart(revBody, err != nil, writer)
			}
			return nil
		})
		return err
	}
	return nil
}

// HTTP handler for a GET of a specific doc attachment
func (h *handler) handleGetAttachment() error {
	docid := h.PathVars()["docid"]
	attachmentName := h.PathVars()["attach"]
	revid := h.getQuery("rev")
	body, err := h.db.GetRev(docid, revid, false, nil)
	if err != nil {
		return err
	}
	if body == nil {
		return kNotFoundError
	}
	meta, ok := db.BodyAttachments(body)[attachmentName].(map[string]interface{})
	if !ok {
		return &base.HTTPError{http.StatusNotFound, "missing " + attachmentName}
	}
	digest := meta["digest"].(string)
	data, err := h.db.GetAttachment(db.AttachmentKey(digest))
	if err != nil {
		return err
	}

	h.setHeader("Etag", digest)
	if contentType, ok := meta["content_type"].(string); ok {
		h.setHeader("Content-Type", contentType)
	}
	if encoding, ok := meta["encoding"].(string); ok {
		h.setHeader("Content-Encoding", encoding)
	}
	h.response.Write(data)
	return nil
}

// HTTP handler for a PUT of a document
func (h *handler) handlePutDoc() error {
	docid := h.PathVars()["docid"]
	body, err := h.readDocument()
	if err != nil {
		return err
	}
	var newRev string

	if h.getQuery("new_edits") != "false" {
		// Regular PUT:
		newRev, err = h.db.Put(docid, body)
		if err != nil {
			return err
		}
		h.setHeader("Etag", newRev)
	} else {
		// Replicator-style PUT with new_edits=false:
		revisions := db.ParseRevisions(body)
		if revisions == nil {
			return &base.HTTPError{http.StatusBadRequest, "Bad _revisions"}
		}
		err = h.db.PutExistingRev(docid, body, revisions)
		if err != nil {
			return err
		}
		newRev = body["_rev"].(string)
	}
	h.writeJSONStatus(http.StatusCreated, db.Body{"ok": true, "id": docid, "rev": newRev})
	return nil
}

// HTTP handler for a POST to a database (creating a document)
func (h *handler) handlePostDoc() error {
	body, err := h.readDocument()
	if err != nil {
		return err
	}
	docid, newRev, err := h.db.Post(body)
	if err != nil {
		return err
	}
	h.setHeader("Location", docid)
	h.setHeader("Etag", newRev)
	h.writeJSON(db.Body{"ok": true, "id": docid, "rev": newRev})
	return nil
}

// HTTP handler for a DELETE of a document
func (h *handler) handleDeleteDoc() error {
	docid := h.PathVars()["docid"]
	revid := h.getQuery("rev")
	newRev, err := h.db.DeleteDoc(docid, revid)
	if err == nil {
		h.writeJSON(db.Body{"ok": true, "id": docid, "rev": newRev})
	}
	return err
}

//////// LOCAL DOCS:

// HTTP handler for a GET of a _local document
func (h *handler) handleGetLocalDoc() error {
	docid := h.PathVars()["docid"]
	value, err := h.db.GetSpecial("local", docid)
	if err != nil {
		return err
	}
	if value == nil {
		return kNotFoundError
	}
	value["_id"] = "_local/" + docid
	value.FixJSONNumbers()
	h.writeJSON(value)
	return nil
}

// HTTP handler for a PUT of a _local document
func (h *handler) handlePutLocalDoc() error {
	docid := h.PathVars()["docid"]
	body, err := h.readJSON()
	if err == nil {
		body.FixJSONNumbers()
		var revid string
		revid, err = h.db.PutSpecial("local", docid, body)
		if err == nil {
			h.writeJSONStatus(http.StatusCreated, db.Body{"ok": true, "id": "_local/" + docid, "rev": revid})
		}
	}
	return err
}

// HTTP handler for a DELETE of a _local document
func (h *handler) handleDelLocalDoc() error {
	docid := h.PathVars()["docid"]
	return h.db.DeleteSpecial("local", docid, h.getQuery("rev"))
}
<commit_msg>REST: Allow ?rev query or If-Match header as alternative to _rev property<commit_after>//  Copyright (c) 2012 Couchbase, Inc.
//  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 rest

import (
	"encoding/json"
	"mime/multipart"
	"net/http"

	"github.com/couchbaselabs/sync_gateway/base"
	"github.com/couchbaselabs/sync_gateway/db"
)

// HTTP handler for a GET of a document
func (h *handler) handleGetDoc() error {
	docid := h.PathVars()["docid"]
	revid := h.getQuery("rev")
	includeRevs := h.getBoolQuery("revs")
	openRevs := h.getQuery("open_revs")

	// What attachment bodies should be included?
	var attachmentsSince []string = nil
	if h.getBoolQuery("attachments") {
		atts := h.getQuery("atts_since")
		if atts != "" {
			err := json.Unmarshal([]byte(atts), &attachmentsSince)
			if err != nil {
				return &base.HTTPError{http.StatusBadRequest, "bad atts_since"}
			}
		} else {
			attachmentsSince = []string{}
		}
	}

	if openRevs == "" {
		// Single-revision GET:
		value, err := h.db.GetRev(docid, revid, includeRevs, attachmentsSince)
		if err != nil {
			return err
		}
		if value == nil {
			return kNotFoundError
		}
		h.setHeader("Etag", value["_rev"].(string))

		hasBodies := (attachmentsSince != nil && value["_attachments"] != nil)
		if h.requestAccepts("multipart/") && (hasBodies || !h.requestAccepts("application/json")) {
			return h.writeMultipart(func(writer *multipart.Writer) error {
				h.db.WriteMultipartDocument(value, writer)
				return nil
			})
		} else {
			h.writeJSON(value)
		}

	} else if openRevs == "all" {
		// open_revs=all:
		return &base.HTTPError{http.StatusNotImplemented, "open_revs=all unimplemented"} // TODO

	} else {
		// open_revs=["id1", "id2", ...]
		attachmentsSince = []string{}
		var revids []string
		err := json.Unmarshal([]byte(openRevs), &revids)
		if err != nil {
			return &base.HTTPError{http.StatusBadRequest, "bad open_revs"}
		}

		err = h.writeMultipart(func(writer *multipart.Writer) error {
			for _, revid := range revids {
				revBody, err := h.db.GetRev(docid, revid, includeRevs, attachmentsSince)
				if err != nil {
					revBody = db.Body{"missing": revid} //TODO: More specific error
				}
				h.db.WriteRevisionAsPart(revBody, err != nil, writer)
			}
			return nil
		})
		return err
	}
	return nil
}

// HTTP handler for a GET of a specific doc attachment
func (h *handler) handleGetAttachment() error {
	docid := h.PathVars()["docid"]
	attachmentName := h.PathVars()["attach"]
	revid := h.getQuery("rev")
	body, err := h.db.GetRev(docid, revid, false, nil)
	if err != nil {
		return err
	}
	if body == nil {
		return kNotFoundError
	}
	meta, ok := db.BodyAttachments(body)[attachmentName].(map[string]interface{})
	if !ok {
		return &base.HTTPError{http.StatusNotFound, "missing " + attachmentName}
	}
	digest := meta["digest"].(string)
	data, err := h.db.GetAttachment(db.AttachmentKey(digest))
	if err != nil {
		return err
	}

	h.setHeader("Etag", digest)
	if contentType, ok := meta["content_type"].(string); ok {
		h.setHeader("Content-Type", contentType)
	}
	if encoding, ok := meta["encoding"].(string); ok {
		h.setHeader("Content-Encoding", encoding)
	}
	h.response.Write(data)
	return nil
}

// HTTP handler for a PUT of a document
func (h *handler) handlePutDoc() error {
	docid := h.PathVars()["docid"]
	body, err := h.readDocument()
	if err != nil {
		return err
	}
	var newRev string

	if h.getQuery("new_edits") != "false" {
		// Regular PUT:
		if oldRev := h.getQuery("rev"); oldRev != "" {
			body["_rev"] = oldRev
		} else if ifMatch := h.rq.Header.Get("If-Match"); ifMatch != "" {
			body["_rev"] = ifMatch
		}
		newRev, err = h.db.Put(docid, body)
		if err != nil {
			return err
		}
		h.setHeader("Etag", newRev)
	} else {
		// Replicator-style PUT with new_edits=false:
		revisions := db.ParseRevisions(body)
		if revisions == nil {
			return &base.HTTPError{http.StatusBadRequest, "Bad _revisions"}
		}
		err = h.db.PutExistingRev(docid, body, revisions)
		if err != nil {
			return err
		}
		newRev = body["_rev"].(string)
	}
	h.writeJSONStatus(http.StatusCreated, db.Body{"ok": true, "id": docid, "rev": newRev})
	return nil
}

// HTTP handler for a POST to a database (creating a document)
func (h *handler) handlePostDoc() error {
	body, err := h.readDocument()
	if err != nil {
		return err
	}
	docid, newRev, err := h.db.Post(body)
	if err != nil {
		return err
	}
	h.setHeader("Location", docid)
	h.setHeader("Etag", newRev)
	h.writeJSON(db.Body{"ok": true, "id": docid, "rev": newRev})
	return nil
}

// HTTP handler for a DELETE of a document
func (h *handler) handleDeleteDoc() error {
	docid := h.PathVars()["docid"]
	revid := h.getQuery("rev")
	if revid == "" {
		revid = h.rq.Header.Get("If-Match")
	}
	newRev, err := h.db.DeleteDoc(docid, revid)
	if err == nil {
		h.writeJSON(db.Body{"ok": true, "id": docid, "rev": newRev})
	}
	return err
}

//////// LOCAL DOCS:

// HTTP handler for a GET of a _local document
func (h *handler) handleGetLocalDoc() error {
	docid := h.PathVars()["docid"]
	value, err := h.db.GetSpecial("local", docid)
	if err != nil {
		return err
	}
	if value == nil {
		return kNotFoundError
	}
	value["_id"] = "_local/" + docid
	value.FixJSONNumbers()
	h.writeJSON(value)
	return nil
}

// HTTP handler for a PUT of a _local document
func (h *handler) handlePutLocalDoc() error {
	docid := h.PathVars()["docid"]
	body, err := h.readJSON()
	if err == nil {
		body.FixJSONNumbers()
		var revid string
		revid, err = h.db.PutSpecial("local", docid, body)
		if err == nil {
			h.writeJSONStatus(http.StatusCreated, db.Body{"ok": true, "id": "_local/" + docid, "rev": revid})
		}
	}
	return err
}

// HTTP handler for a DELETE of a _local document
func (h *handler) handleDelLocalDoc() error {
	docid := h.PathVars()["docid"]
	return h.db.DeleteSpecial("local", docid, h.getQuery("rev"))
}
<|endoftext|>


<commit_before>// service for exchanging current user settings with UI
package settings

import (
	"sync"

	"github.com/getlantern/flashlight/analytics"
	"github.com/getlantern/flashlight/config"

	"github.com/getlantern/flashlight/ui"
	"github.com/getlantern/golog"
)

const (
	messageType = `Settings`
)

var (
	log      = golog.LoggerFor("flashlight.settings")
	service  *ui.Service
	cfgMutex sync.Mutex
)

type Settings struct {
	Version    string
	BuildDate  string
	AutoReport bool
}

func Configure(cfg *config.Config, version, buildDate string) {
	cfgMutex.Lock()
	defer cfgMutex.Unlock()

	// base settings are always written
	baseSettings := &Settings{
		Version:    version,
		BuildDate:  buildDate,
		AutoReport: cfg.AutoReport,
	}

	if service == nil {
		err := start(baseSettings)
		if err != nil {
			log.Errorf("Unable to register settings service: %q", err)
			return
		}
		go read()
	}
}

func start(baseSettings *Settings) error {
	var err error
	helloFn := func(write func(interface{}) error) error {
		log.Debugf("Sending Lantern settings to new client")
		return write(baseSettings)
	}

	// start GA service
	if baseSettings.AutoReport {
		analytics.Start()
	}

	service, err = ui.Register(messageType, nil, helloFn)
	return err
}

func read() {
	for msg := range service.In {
		settings := (msg).(map[string]interface{})
		config.Update(func(updated *config.Config) error {
			updated.AutoReport = settings["autoReport"].(bool)
			return nil
		})
	}
}
<commit_msg>initial commit<commit_after>// service for exchanging current user settings with UI
package settings

import (
	"sync"

	"github.com/getlantern/flashlight/analytics"
	"github.com/getlantern/flashlight/config"

	"github.com/getlantern/flashlight/ui"
	"github.com/getlantern/golog"
)

const (
	messageType = `Settings`
)

var (
	log          = golog.LoggerFor("flashlight.settings")
	service      *ui.Service
	cfgMutex     sync.Mutex
	baseSettings *Settings
)

type Settings struct {
	Version    string
	BuildDate  string
	AutoReport bool
}

func Configure(cfg *config.Config, version, buildDate string) {
	cfgMutex.Lock()
	defer cfgMutex.Unlock()

	// base settings are always written
	baseSettings = &Settings{
		Version:    version,
		BuildDate:  buildDate,
		AutoReport: cfg.AutoReport,
	}

	if service == nil {
		err := start(baseSettings)
		if err != nil {
			log.Errorf("Unable to register settings service: %q", err)
			return
		}
		go read()
	}
}

func start(baseSettings *Settings) error {
	var err error
	helloFn := func(write func(interface{}) error) error {
		log.Debugf("Sending Lantern settings to new client")
		return write(baseSettings)
	}

	// start GA service
	if baseSettings.AutoReport {
		analytics.Start()
	}

	service, err = ui.Register(messageType, nil, helloFn)
	return err
}

func read() {
	for msg := range service.In {
		settings := (msg).(map[string]interface{})
		config.Update(func(updated *config.Config) error {
			if autoReport, exists := settings["autoReport"]; exists {
				baseSettings.AutoReport = autoReport.(bool)
				updated.AutoReport = autoReport.(bool)

			}
			return nil
		})
	}
}
<|endoftext|>