<commit_before>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gaesessions

import (
	"bytes"
	"encoding/base32"
	"encoding/gob"
	"net/http"
	"strings"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/delay"
	"appengine/memcache"
	"appengine/taskqueue"

	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
)

// MemcacheDatastoreStore -----------------------------------------------------

// NewMemcacheDatastoreStore returns a new MemcacheDatastoreStore.
//
// The kind argument is the kind name used to store the session data.
// If empty it will use "Session".
//
// See NewCookieStore() for a description of the other parameters.
func NewMemcacheDatastoreStore(kind, keyPrefix string, keyPairs ...[]byte) *MemcacheDatastoreStore {
	if kind == "" {
		kind = "Session"
	}
	if keyPrefix == "" {
		keyPrefix = "gorilla.appengine.sessions."
	}
	return &MemcacheDatastoreStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		kind:   kind,
		prefix: keyPrefix,
	}
}

type MemcacheDatastoreStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
	kind    string
	prefix  string
}

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

// New returns a session for the given name without adding it to the registry.
//
// See CookieStore.New().
func (s *MemcacheDatastoreStore) New(r *http.Request, name string) (*sessions.Session,
	error) {
	session := sessions.NewSession(s, name)
	session.Options = &(*s.Options)
	session.IsNew = true
	var err error
	if cookie, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, cookie.Value, &session.ID,
			s.Codecs...)
		if err == nil {
			c := appengine.NewContext(r)
			err = loadFromMemcache(c, session)
			if err == memcache.ErrCacheMiss {
				err = loadFromDatastore(c, s.kind, session)
			}
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *MemcacheDatastoreStore) Save(r *http.Request, w http.ResponseWriter,
	session *sessions.Session) error {
	if session.ID == "" {
		session.ID = s.prefix +
			strings.TrimRight(
				base32.StdEncoding.EncodeToString(
					securecookie.GenerateRandomKey(32)), "=")
	}
	c := appengine.NewContext(r)
	if err := saveToMemcache(c, session); err != nil {
		return err
	}
	if err := saveToDatastore(c, s.kind, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded,
		session.Options))
	return nil
}

// DatastoreStore -------------------------------------------------------------

// Session is used to load and save session data in the datastore.
type Session struct {
	Date           time.Time
	ExpirationDate time.Time
	Value          []byte
}

// NewDatastoreStore returns a new DatastoreStore.
//
// The kind argument is the kind name used to store the session data.
// If empty it will use "Session".
//
// See NewCookieStore() for a description of the other parameters.
func NewDatastoreStore(kind string, keyPairs ...[]byte) *DatastoreStore {
	if kind == "" {
		kind = "Session"
	}
	return &DatastoreStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		kind: kind,
	}
}

// DatastoreStore stores sessions in the App Engine datastore.
type DatastoreStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
	kind    string
}

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

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

// Save adds a single session to the response.
func (s *DatastoreStore) Save(r *http.Request, w http.ResponseWriter,
	session *sessions.Session) error {
	if session.ID == "" {
		session.ID =
			strings.TrimRight(
				base32.StdEncoding.EncodeToString(
					securecookie.GenerateRandomKey(32)), "=")
	}
	c := appengine.NewContext(r)
	if err := saveToDatastore(c, s.kind, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded,
		session.Options))
	return nil
}

// save writes encoded session.Values to datastore.
func saveToDatastore(c appengine.Context, kind string,
	session *sessions.Session) error {
	if len(session.Values) == 0 {
		// Don't need to write anything.
		return nil
	}
	serialized, err := serialize(session.Values)
	if err != nil {
		return err
	}
	k := datastore.NewKey(c, kind, session.ID, 0, nil)
	now := time.Now()
	var expirationDate time.Time
	if session.Options.MaxAge > 0 {
		expiration := time.Duration(session.Options.MaxAge) * time.Second
		expirationDate = now.Add(expiration)

		k, err = datastore.Put(c, k, &Session{
			Date:           now,
			ExpirationDate: expirationDate,
			Value:          serialized,
		})
		if err != nil {
			return err
		}

		task, err := expireSessionLater.Task(kind, session.ID)
		if err != nil {
			return err
		}
		task.ETA = expirationDate
		task, err = taskqueue.Add(c, task, "")
		if err != nil {
			return err
		}
	} else {
		err = datastore.Delete(c, k)
		if err != nil {
			return err
		}
	}
	return nil
}

// load gets a value from datastore and decodes its content into
// session.Values.
func loadFromDatastore(c appengine.Context, kind string,
	session *sessions.Session) error {
	k := datastore.NewKey(c, kind, session.ID, 0, nil)
	entity := Session{}
	if err := datastore.Get(c, k, &entity); err != nil {
		return err
	}
	if err := deserialize(entity.Value, &session.Values); err != nil {
		return err
	}
	return nil
}

var expireSessionLater = delay.Func("expireSession", expireSession)

func expireSession(c appengine.Context, kind, sessionID string) error {
	c.Debugf("DatastoreStore expireSession start session.ID=%s", sessionID)
	k := datastore.NewKey(c, kind, sessionID, 0, nil)
	entity := Session{}
	if err := datastore.Get(c, k, &entity); err != nil {
		if err == datastore.ErrNoSuchEntity {
			// Already deleted. Do nothing.
			return nil
		}
		c.Errorf("DatastoreStore expireSession datastore.Get failed. session.ID=%s, err=%s", sessionID, err.Error())
		return err
	}
	session := sessions.Session{
		Values: make(map[interface{}]interface{}),
	}
	if err := deserialize(entity.Value, &session.Values); err != nil {
		c.Errorf("DatastoreStore expireSession deserialize failed. session.ID=%s, err=%s", sessionID, err.Error())
		return err
	}
	now := time.Now()
	if now.After(entity.ExpirationDate) {
		err := datastore.Delete(c, k)
		if err != nil {
			c.Errorf("DatastoreStore expireSession delete session.ID=%s, now=%s, expirationDate=%s, err=%s", sessionID, now, entity.ExpirationDate, err.Error())
			return err
		}
		c.Debugf("DatastoreStore expireSession delete done. session.ID=%s", sessionID)
	}
	return nil
}

// MemcacheStore --------------------------------------------------------------

// NewMemcacheStore returns a new MemcacheStore.
//
// The keyPrefix argument is the prefix used for memcache keys. If empty it
// will use "gorilla.appengine.sessions.".
//
// See NewCookieStore() for a description of the other parameters.
func NewMemcacheStore(keyPrefix string, keyPairs ...[]byte) *MemcacheStore {
	if keyPrefix == "" {
		keyPrefix = "gorilla.appengine.sessions."
	}
	return &MemcacheStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		prefix: keyPrefix,
	}
}

// MemcacheStore stores sessions in the App Engine memcache.
type MemcacheStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
	prefix  string
}

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

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

// Save adds a single session to the response.
func (s *MemcacheStore) Save(r *http.Request, w http.ResponseWriter,
	session *sessions.Session) error {
	if session.ID == "" {
		session.ID = s.prefix +
			strings.TrimRight(
				base32.StdEncoding.EncodeToString(
					securecookie.GenerateRandomKey(32)), "=")
	}
	c := appengine.NewContext(r)
	if err := saveToMemcache(c, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded,
		session.Options))
	return nil
}

// save writes encoded session.Values to memcache.
func saveToMemcache(c appengine.Context, session *sessions.Session) error {
	if len(session.Values) == 0 {
		// Don't need to write anything.
		return nil
	}
	serialized, err := serialize(session.Values)
	if err != nil {
		return err
	}
	var expiration time.Duration
	if session.Options.MaxAge > 0 {
		expiration = time.Duration(session.Options.MaxAge) * time.Second
		c.Debugf("MemcacheStore.save. session.ID=%s, expiration=%s",
			session.ID, expiration)
		err = memcache.Set(c, &memcache.Item{
			Key:        session.ID,
			Value:      serialized,
			Expiration: expiration,
		})
		if err != nil {
			return err
		}
	} else {
		err = memcache.Delete(c, session.ID)
		if err != nil {
			return err
		}
		c.Debugf("MemcacheStore.save. delete session.ID=%s", session.ID)
	}
	return nil
}

// load gets a value from memcache and decodes its content into session.Values.
func loadFromMemcache(c appengine.Context, session *sessions.Session) error {
	item, err := memcache.Get(c, session.ID)
	if err != nil {
		return err
	}
	if err := deserialize(item.Value, &session.Values); err != nil {
		return err
	}
	return nil
}

// Serialization --------------------------------------------------------------

// serialize encodes a value using gob.
func serialize(src interface{}) ([]byte, error) {
	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	if err := enc.Encode(src); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// deserialize decodes a value using gob.
func deserialize(src []byte, dst interface{}) error {
	dec := gob.NewDecoder(bytes.NewBuffer(src))
	if err := dec.Decode(dst); err != nil {
		return err
	}
	return nil
}
<commit_msg>Add RemoveExpiredDatastoreSessions function which you can call from a cron job. Stop using taskqueue to remove expired sessions to avoid errors when the ETA exceeds 30 days limit.<commit_after>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gaesessions

import (
	"bytes"
	"encoding/base32"
	"encoding/gob"
	"net/http"
	"strings"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/memcache"

	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
)

// MemcacheDatastoreStore -----------------------------------------------------

const defaultKind = "Session"

// NewMemcacheDatastoreStore returns a new MemcacheDatastoreStore.
//
// The kind argument is the kind name used to store the session data.
// If empty it will use "Session".
//
// See NewCookieStore() for a description of the other parameters.
func NewMemcacheDatastoreStore(kind, keyPrefix string, keyPairs ...[]byte) *MemcacheDatastoreStore {
	if kind == "" {
		kind = defaultKind
	}
	if keyPrefix == "" {
		keyPrefix = "gorilla.appengine.sessions."
	}
	return &MemcacheDatastoreStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		kind:   kind,
		prefix: keyPrefix,
	}
}

type MemcacheDatastoreStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
	kind    string
	prefix  string
}

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

// New returns a session for the given name without adding it to the registry.
//
// See CookieStore.New().
func (s *MemcacheDatastoreStore) New(r *http.Request, name string) (*sessions.Session,
	error) {
	session := sessions.NewSession(s, name)
	session.Options = &(*s.Options)
	session.IsNew = true
	var err error
	if cookie, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, cookie.Value, &session.ID,
			s.Codecs...)
		if err == nil {
			c := appengine.NewContext(r)
			err = loadFromMemcache(c, session)
			if err == memcache.ErrCacheMiss {
				err = loadFromDatastore(c, s.kind, session)
			}
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

// Save adds a single session to the response.
func (s *MemcacheDatastoreStore) Save(r *http.Request, w http.ResponseWriter,
	session *sessions.Session) error {
	if session.ID == "" {
		session.ID = s.prefix +
			strings.TrimRight(
				base32.StdEncoding.EncodeToString(
					securecookie.GenerateRandomKey(32)), "=")
	}
	c := appengine.NewContext(r)
	if err := saveToMemcache(c, session); err != nil {
		return err
	}
	if err := saveToDatastore(c, s.kind, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded,
		session.Options))
	return nil
}

// DatastoreStore -------------------------------------------------------------

// Session is used to load and save session data in the datastore.
type Session struct {
	Date           time.Time
	ExpirationDate time.Time
	Value          []byte
}

// NewDatastoreStore returns a new DatastoreStore.
//
// The kind argument is the kind name used to store the session data.
// If empty it will use "Session".
//
// See NewCookieStore() for a description of the other parameters.
func NewDatastoreStore(kind string, keyPairs ...[]byte) *DatastoreStore {
	if kind == "" {
		kind = "Session"
	}
	return &DatastoreStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		kind: kind,
	}
}

// DatastoreStore stores sessions in the App Engine datastore.
type DatastoreStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
	kind    string
}

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

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

// Save adds a single session to the response.
func (s *DatastoreStore) Save(r *http.Request, w http.ResponseWriter,
	session *sessions.Session) error {
	if session.ID == "" {
		session.ID =
			strings.TrimRight(
				base32.StdEncoding.EncodeToString(
					securecookie.GenerateRandomKey(32)), "=")
	}
	c := appengine.NewContext(r)
	if err := saveToDatastore(c, s.kind, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded,
		session.Options))
	return nil
}

// save writes encoded session.Values to datastore.
func saveToDatastore(c appengine.Context, kind string,
	session *sessions.Session) error {
	if len(session.Values) == 0 {
		// Don't need to write anything.
		return nil
	}
	serialized, err := serialize(session.Values)
	if err != nil {
		return err
	}
	k := datastore.NewKey(c, kind, session.ID, 0, nil)
	now := time.Now()
	var expirationDate time.Time
	if session.Options.MaxAge > 0 {
		expiration := time.Duration(session.Options.MaxAge) * time.Second
		expirationDate = now.Add(expiration)

		k, err = datastore.Put(c, k, &Session{
			Date:           now,
			ExpirationDate: expirationDate,
			Value:          serialized,
		})
		if err != nil {
			return err
		}
	} else {
		err = datastore.Delete(c, k)
		if err != nil {
			return err
		}
	}
	return nil
}

// load gets a value from datastore and decodes its content into
// session.Values.
func loadFromDatastore(c appengine.Context, kind string,
	session *sessions.Session) error {
	k := datastore.NewKey(c, kind, session.ID, 0, nil)
	entity := Session{}
	if err := datastore.Get(c, k, &entity); err != nil {
		return err
	}
	if err := deserialize(entity.Value, &session.Values); err != nil {
		return err
	}
	return nil
}

// remove expired sessions in the datastore. you can call this function
// from a cron job.
//
// sample handler config in app.yaml:
// handlers:
// - url: /tasks/removeExpiredSessions
//   script: _go_app
//   login: admin
// - url: /.*
//   script: _go_app
//
// handler registration code:
// http.HandleFunc("/tasks/removeExpiredSessions", removeExpiredSessionsHandler)
//
// sample handler:
// func removeExpiredSessionsHandler(w http.ResponseWriter, r *http.Request) {
//	c := appengine.NewContext(r)
//	gaesessions.RemoveExpiredDatastoreSessions(c, "")
// }
//
// sample cron.yaml:
// cron:
// - description: expired session removal job
//   url: /tasks/removeExpiredSessions
//   schedule: every 1 minutes
func RemoveExpiredDatastoreSessions(c appengine.Context, kind string) error {
	keys, err := findExpiredDatastoreSessionKeys(c, kind)
	if err != nil {
		return err
	}
	return datastore.DeleteMulti(c, keys)
}

func findExpiredDatastoreSessionKeys(c appengine.Context, kind string) (keys []*datastore.Key, err error) {
	if kind == "" {
		kind = defaultKind
	}
	now := time.Now()
	q := datastore.NewQuery(kind).Filter("ExpirationDate <=", now).KeysOnly()
	keys, err = q.GetAll(c, nil)
	return
}

// MemcacheStore --------------------------------------------------------------

// NewMemcacheStore returns a new MemcacheStore.
//
// The keyPrefix argument is the prefix used for memcache keys. If empty it
// will use "gorilla.appengine.sessions.".
//
// See NewCookieStore() for a description of the other parameters.
func NewMemcacheStore(keyPrefix string, keyPairs ...[]byte) *MemcacheStore {
	if keyPrefix == "" {
		keyPrefix = "gorilla.appengine.sessions."
	}
	return &MemcacheStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		prefix: keyPrefix,
	}
}

// MemcacheStore stores sessions in the App Engine memcache.
type MemcacheStore struct {
	Codecs  []securecookie.Codec
	Options *sessions.Options // default configuration
	prefix  string
}

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

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

// Save adds a single session to the response.
func (s *MemcacheStore) Save(r *http.Request, w http.ResponseWriter,
	session *sessions.Session) error {
	if session.ID == "" {
		session.ID = s.prefix +
			strings.TrimRight(
				base32.StdEncoding.EncodeToString(
					securecookie.GenerateRandomKey(32)), "=")
	}
	c := appengine.NewContext(r)
	if err := saveToMemcache(c, session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		s.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded,
		session.Options))
	return nil
}

// save writes encoded session.Values to memcache.
func saveToMemcache(c appengine.Context, session *sessions.Session) error {
	if len(session.Values) == 0 {
		// Don't need to write anything.
		return nil
	}
	serialized, err := serialize(session.Values)
	if err != nil {
		return err
	}
	var expiration time.Duration
	if session.Options.MaxAge > 0 {
		expiration = time.Duration(session.Options.MaxAge) * time.Second
		c.Debugf("MemcacheStore.save. session.ID=%s, expiration=%s",
			session.ID, expiration)
		err = memcache.Set(c, &memcache.Item{
			Key:        session.ID,
			Value:      serialized,
			Expiration: expiration,
		})
		if err != nil {
			return err
		}
	} else {
		err = memcache.Delete(c, session.ID)
		if err != nil {
			return err
		}
		c.Debugf("MemcacheStore.save. delete session.ID=%s", session.ID)
	}
	return nil
}

// load gets a value from memcache and decodes its content into session.Values.
func loadFromMemcache(c appengine.Context, session *sessions.Session) error {
	item, err := memcache.Get(c, session.ID)
	if err != nil {
		return err
	}
	if err := deserialize(item.Value, &session.Values); err != nil {
		return err
	}
	return nil
}

// Serialization --------------------------------------------------------------

// serialize encodes a value using gob.
func serialize(src interface{}) ([]byte, error) {
	buf := new(bytes.Buffer)
	enc := gob.NewEncoder(buf)
	if err := enc.Encode(src); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// deserialize decodes a value using gob.
func deserialize(src []byte, dst interface{}) error {
	dec := gob.NewDecoder(bytes.NewBuffer(src))
	if err := dec.Decode(dst); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"

	flag "github.com/jessevdk/go-flags"
)

//Settings are concrete and stored in configuration file
type Settings struct {
	ImageDir string `long:"dir" description:"Target Directory" default:"img" ini-name:"downdir"`
	QDepth   int    `short:"q" long:"queue" description:"Length of the queue buffer" default:"20" ini-name:"queue_depth"`
	Key      string `short:"k" long:"key" description:"Derpibooru API key" ini-name:"key"`
}

//FlagOpts are runtime boolean flags
type FlagOpts struct {
	Unsafe  bool `long:"unsafe" description:"If set, trusts in unknown authority"`
	NoHTTPS bool `long:"nohttps" description:"Disable HTTPS and try to download insecurely"`
}

//FiltOpts are filtration parameters
type FiltOpts struct {
	Filter bool `no-flag:" " short:"f" long:"filter" description:"If set, enables client-side filtering of downloaded images"`
	Score  int  `long:"score" description:"Filter option, minimal score of image for it to be downloaded"`
}

//TagOpts are options relevant to searching by tags
type TagOpts struct {
	Tag       string `short:"t" long:"tag" description:"Tag to download"`
	StartPage int    `short:"p" long:"startpage" description:"Starting page for search" default:"1"`
	StopPage  int    `short:"n" long:"stoppage" description:"Stopping page for search, default - parse all search pages"`
}

//Options provide program-wide options. At maximum, we got one persistent global and one short-living copy for writing in config file
type Options struct {
	*Settings
	*FlagOpts
	*FiltOpts
	*TagOpts
	Args struct {
		IDs []int `description:"Image IDs to download" optional:"yes"`
	} `positional-args:"yes"`
}

//WriteConfig writes default, presumably sensible configuration into file.
func (sets *Settings) WriteConfig(oldsets *Settings) {

	if sets.compareStatic(oldsets) { //If nothing to write, no double-writing files
		return
	}

	inifile, err := os.OpenFile("config.ini", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)

	if err != nil {
		lFatal("Could  not create configuration file")
		//Need to check if log file is created before config file. Also, work around screams
		//in case if nor log, nor config file could be created.
	}

	defer func() {
		err = inifile.Close()
		if err != nil {
			lFatal("Could  not close configuration file")
		}
	}()

	err = sets.prettyWriteIni(inifile)

	if err != nil {
		lFatal("Could  not write in configuration file")
	}
}

func (sets Settings) prettyWriteIni(inifile io.Writer) error {
	tb := tabwriter.NewWriter(inifile, 10, 8, 0, ' ', 0) //Tabs! Elastic! Pretty!

	fmt.Fprintf(tb, "key \t= %s\n", sets.Key)
	fmt.Fprintf(tb, "queue_depth \t= %s\n", strconv.Itoa(sets.QDepth))
	fmt.Fprintf(tb, "downdir \t= %s\n", sets.ImageDir)

	return tb.Flush()
}

//compareStatic compares only options I want to preserve across launches.
func (sets *Settings) compareStatic(b *Settings) bool {
	if b == nil {
		return false
	}
	if sets.ImageDir == b.ImageDir &&
		sets.QDepth == b.QDepth &&
		sets.Key == b.Key {
		return true
	}
	return false
}

//configSetup reads static config from file and runtime options from commandline
//It also preserves static config for later comparsion with runtime to prevent
//rewriting it when no changes are made
func (opts *Options) configSetup() ([]string, *Settings) {
	err := flag.IniParse("config.ini", opts)
	if err != nil {
		switch err.(type) {
		default:
			panic(err)
		case *os.PathError:
			lWarn("config.ini not found, using defaults")
		}
	}
	inisets := *opts.Settings

	args, err := flag.Parse(opts)
	checkFlagError(err)

	for _, arg := range os.Args {
		if strings.Contains(arg, "--score") {
			opts.Filter = true
		}
	}
	return args, &inisets
}

func getOptions() (opts *Options, args []string) {
	opts = new(Options)
	args, inisets := opts.configSetup()
	opts.Settings.WriteConfig(inisets)
	return
}

func checkFlagError(err error) {
	if err == nil {
		return
	}

	flagError := err.(*flag.Error)

	switch flagError.Type {
	case flag.ErrHelp:
		fallthrough
	case flag.ErrUnknownFlag:
		fmt.Println("Use --help to view all available options")
		os.Exit(0)
	default:
		lFatal("Can't parse flags: %s\n", err)
	}
}
<commit_msg>More renaming and reshuffling, less panic Finally closes #13<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"

	flag "github.com/jessevdk/go-flags"
)

//Settings are concrete and stored in configuration file
type Settings struct {
	ImageDir string `long:"dir" description:"Target Directory" default:"img" ini-name:"downdir"`
	QDepth   int    `short:"q" long:"queue" description:"Length of the queue buffer" default:"20" ini-name:"queue_depth"`
	Key      string `short:"k" long:"key" description:"Derpibooru API key" ini-name:"key"`
}

//FlagOpts are runtime boolean flags
type FlagOpts struct {
	Unsafe  bool `long:"unsafe" description:"If set, trusts in unknown authority"`
	NoHTTPS bool `long:"nohttps" description:"Disable HTTPS and try to download insecurely"`
}

//FiltOpts are filtration parameters
type FiltOpts struct {
	Filter bool `no-flag:" " short:"f" long:"filter" description:"If set, enables client-side filtering of downloaded images"`
	Score  int  `long:"score" description:"Filter option, minimal score of image for it to be downloaded"`
}

//TagOpts are options relevant to searching by tags
type TagOpts struct {
	Tag       string `short:"t" long:"tag" description:"Tag to download"`
	StartPage int    `short:"p" long:"startpage" description:"Starting page for search" default:"1"`
	StopPage  int    `short:"n" long:"stoppage" description:"Stopping page for search, default - parse all search pages"`
}

//Options provide program-wide options. At maximum, we got one persistent global and one short-living copy for writing in config file
type Options struct {
	*Settings
	*FlagOpts
	*FiltOpts
	*TagOpts
	Args struct {
		IDs []int `description:"Image IDs to download" optional:"yes"`
	} `positional-args:"yes"`
}

func getOptions() (opts *Options, args []string) {
	opts = new(Options)
	args, inisets := opts.configSetup()
	opts.Settings.checkedWriteIni(inisets)
	return
}

//checkedWriteIni writes default, presumably sensible configuration into file without overwriting old one if unchanged
func (sets *Settings) checkedWriteIni(oldsets *Settings) {
	if sets.isEqual(oldsets) { //If nothing to write, no double-writing files
		return
	}

	inifile, err := os.OpenFile("config.ini", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)

	if err != nil {
		lFatal("Could  not create configuration file")
	}

	defer func() {
		err = inifile.Close()
		if err != nil {
			lFatal("Could  not close configuration file")
		}
	}()

	err = sets.prettyWriteIni(inifile)

	if err != nil {
		lFatal("Could  not write in configuration file")
	}
}

//prettyWriteIni Uses tabwriter to make pretty ini file with
func (sets Settings) prettyWriteIni(inifile io.Writer) error {
	tb := tabwriter.NewWriter(inifile, 10, 8, 0, ' ', 0) //Tabs! Elastic! Pretty!

	fmt.Fprintf(tb, "key \t= %s\n", sets.Key)
	fmt.Fprintf(tb, "queue_depth \t= %s\n", strconv.Itoa(sets.QDepth))
	fmt.Fprintf(tb, "downdir \t= %s\n", sets.ImageDir)

	return tb.Flush()
}

//compareStatic compares only options I want to preserve across launches.
func (sets *Settings) isEqual(b *Settings) bool {
	if b == nil {
		return false
	}
	if sets.ImageDir == b.ImageDir &&
		sets.QDepth == b.QDepth &&
		sets.Key == b.Key {
		return true
	}
	return false
}

//configSetup reads static config from file and runtime options from commandline
//It also preserves static config for later comparsion with runtime to prevent
//rewriting it when no changes are made
func (opts *Options) configSetup() ([]string, *Settings) {
	err := flag.IniParse("config.ini", opts)
	if err != nil {
		switch err.(type) {
		default:
			lFatal(err)
		case *os.PathError:
			lWarn("config.ini not found, using defaults")
		}
	}
	inisets := *opts.Settings //copy value instead of reference - or we will get no results later

	args, err := flag.Parse(opts)
	checkFlagError(err) //Here we scream if something goes wrong and provide help if something goes meh.

	for _, arg := range os.Args {
		if strings.Contains(arg, "--score") {
			opts.Filter = true
		}
	}
	return args, &inisets
}

func checkFlagError(err error) {
	if err == nil {
		return
	}

	flagError := err.(*flag.Error)

	switch flagError.Type {
	case flag.ErrHelp:
		os.Exit(0) //Why fall through when asked for help? Just exit with suggestion
	case flag.ErrUnknownFlag:
		fmt.Println("Use --help to view all available options")
		os.Exit(0)
	default:
		lFatal("Can't parse flags: %s\n", err)
	}
}
<|endoftext|>


<commit_before>// Package querybuilder parses ResourceD query and turn it into Cassandra + Lucene query.
package querybuilder

import (
	"fmt"
	"strings"

	"github.com/resourced/resourced-master/libstring"
)

// Parse parses ResourceD query and turns it into Cassandra + Lucene query.
func Parse(input string) string {
	return parseAnd(input)
}

// parseAnd parses and conjunctive operator.
func parseAnd(input string) string {
	pgQueryParts := make([]string, 0)

	// normalize variation of AND
	input = strings.Replace(input, " AND ", " and ", -1)

	statements := strings.Split(input, " and ")
	for _, statement := range statements {
		pgStatement := parseStatement(statement)
		if pgStatement != "" {
			pgQueryParts = append(pgQueryParts, pgStatement)
		}
	}

	if len(pgQueryParts) > 1 {
		return fmt.Sprintf(`{type: "boolean", must: [%v]}`, strings.Join(pgQueryParts, ","))

	} else if len(pgQueryParts) == 1 {
		return pgQueryParts[0]
	}

	return ""
}

// parseStringField parses ResourceD "string" query and turns it into lucene search condition.
func parseStringField(statement, field, operator string) string {
	parts := strings.Split(statement, operator)

	data := parts[len(parts)-1]
	data = strings.TrimSpace(data)
	data = libstring.StripChars(data, `"'`)

	if operator == "contains" {
		dataSlice := strings.Split(data, ",")

		for i, datum := range dataSlice {
			dataSlice[i] = `"` + strings.TrimSpace(datum) + `"`
		}

		return fmt.Sprintf(`{type: "%v", field: "%v", values: "%v"}`, operator, field, strings.Join(dataSlice, `,`))
	}

	return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, operator, field, data)
}

// parseFullTextSearchField parses ResourceD "search" query and turns it into postgres statement.
// Operator is "search" in this context.
func parseFullTextSearchField(statement, field, operator string) string {
	parts := strings.Split(statement, operator)

	searchQuery := parts[len(parts)-1]
	searchQuery = strings.TrimSpace(searchQuery)
	searchQuery = libstring.StripChars(searchQuery, `"'`)

	return fmt.Sprintf(`{type: "phrase", field: "logline", value: "%v", slop: 1}`, searchQuery)
}

// parseStatement parses ResourceD statement and turns it into postgres statement.
func parseStatement(statement string) string {
	statement = strings.TrimSpace(statement)

	// Querying tags.
	// There can only be 1 operator for tags: "="
	if strings.HasPrefix(statement, "Tags") || strings.HasPrefix(statement, "tags") {
		if strings.Contains(statement, "=") {
			parts := strings.Split(statement, "=")

			field := strings.Replace(parts[0], ".", "$", 1)

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "match", field, data)
		}
	}

	// Querying hostname.
	// Operators:
	// "="        : Exact match.
	// "^"        : Starts with, case sensitive.
	// "~"        : Matches regular expression, case sensitive.
	// "contains" : Contains the following values.
	// "wildcard" : Perform wildcard search.
	if strings.HasPrefix(statement, "Hostname") || strings.HasPrefix(statement, "hostname") {
		if strings.Contains(statement, "=") {
			parts := strings.Split(statement, "=")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "match", "hostname", data)

		} else if strings.Contains(statement, "contains") {
			return parseStringField(statement, "hostname", "contains")

		} else if strings.Contains(statement, "wildcard") {
			return parseStringField(statement, "hostname", "wildcard")

		} else if strings.Contains(statement, "^") {
			return parseStringField(statement, "hostname", "prefix")

		} else if strings.Contains(statement, "~") {
			return parseStringField(statement, "hostname", "regexp")
		}
	}

	// Querying filename.
	// Operators:
	// "="        : Exact match.
	// "^"        : Starts with, case sensitive.
	// "~"        : Matches regular expression, case sensitive.
	// "contains" : Contains the following values.
	// "wildcard" : Perform wildcard search.
	if strings.HasPrefix(statement, "Filename") || strings.HasPrefix(statement, "filename") {
		if strings.Contains(statement, "=") {
			parts := strings.Split(statement, "=")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "match", "filename", data)

		} else if strings.Contains(statement, "contains") {
			return parseStringField(statement, "filename", "contains")

		} else if strings.Contains(statement, "wildcard") {
			return parseStringField(statement, "filename", "wildcard")

		} else if strings.Contains(statement, "^") {
			return parseStringField(statement, "filename", "prefix")

		} else if strings.Contains(statement, "~") {
			return parseStringField(statement, "filename", "regexp")
		}
	}

	// Querying logline.
	// Operators:
	// "search" : Full text search.
	if strings.HasPrefix(statement, "Logline") || strings.HasPrefix(statement, "logline") {
		if strings.Contains(statement, "search") {
			return parseFullTextSearchField(statement, "logline", "search")
		}
	}

	return ""
}
<commit_msg>Fix contains, wildcard, prefix, and regexp.<commit_after>// Package querybuilder parses ResourceD query and turn it into Cassandra + Lucene query.
package querybuilder

import (
	"fmt"
	"strings"

	"github.com/resourced/resourced-master/libstring"
)

// Parse parses ResourceD query and turns it into Cassandra + Lucene query.
func Parse(input string) string {
	return parseAnd(input)
}

// parseAnd parses and conjunctive operator.
func parseAnd(input string) string {
	pgQueryParts := make([]string, 0)

	// normalize variation of AND
	input = strings.Replace(input, " AND ", " and ", -1)

	statements := strings.Split(input, " and ")
	for _, statement := range statements {
		pgStatement := parseStatement(statement)
		if pgStatement != "" {
			pgQueryParts = append(pgQueryParts, pgStatement)
		}
	}

	if len(pgQueryParts) > 1 {
		return fmt.Sprintf(`{type: "boolean", must: [%v]}`, strings.Join(pgQueryParts, ","))

	} else if len(pgQueryParts) == 1 {
		return pgQueryParts[0]
	}

	return ""
}

// parseFullTextSearchField parses ResourceD "search" query and turns it into postgres statement.
// Operator is "search" in this context.
func parseFullTextSearchField(statement, field, operator string) string {
	parts := strings.Split(statement, operator)

	searchQuery := parts[len(parts)-1]
	searchQuery = strings.TrimSpace(searchQuery)
	searchQuery = libstring.StripChars(searchQuery, `"'`)

	return fmt.Sprintf(`{type: "phrase", field: "logline", value: "%v", slop: 1}`, searchQuery)
}

// parseStatement parses ResourceD statement and turns it into postgres statement.
func parseStatement(statement string) string {
	statement = strings.TrimSpace(statement)

	// Querying tags.
	// There can only be 1 operator for tags: "="
	if strings.HasPrefix(statement, "Tags") || strings.HasPrefix(statement, "tags") {
		if strings.Contains(statement, "=") {
			parts := strings.Split(statement, "=")

			field := strings.Replace(parts[0], ".", "$", 1)

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "match", field, data)
		}
	}

	// Querying hostname.
	// Operators:
	// "="        : Exact match.
	// "^"        : Starts with, case sensitive.
	// "~"        : Matches regular expression, case sensitive.
	// "contains" : Contains the following values.
	// "wildcard" : Perform wildcard search.
	if strings.HasPrefix(statement, "Hostname") || strings.HasPrefix(statement, "hostname") {
		if strings.Contains(statement, "=") {
			parts := strings.Split(statement, "=")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "match", "hostname", data)

		} else if strings.Contains(statement, "contains") {
			parts := strings.Split(statement, "contains")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			dataSlice := strings.Split(data, ",")
			for i, datum := range dataSlice {
				dataSlice[i] = `"` + strings.TrimSpace(datum) + `"`
			}

			return fmt.Sprintf(`{type: "%v", field: "%v", values: "%v"}`, "contains", "hostname", strings.Join(dataSlice, `,`))

		} else if strings.Contains(statement, "wildcard") {
			parts := strings.Split(statement, "wildcard")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "wildcard", "hostname", data)

		} else if strings.Contains(statement, "^") {
			parts := strings.Split(statement, "^")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "prefix", "hostname", data)

		} else if strings.Contains(statement, "~") {
			parts := strings.Split(statement, "~")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "regexp", "hostname", data)
		}
	}

	// Querying filename.
	// Operators:
	// "="        : Exact match.
	// "^"        : Starts with, case sensitive.
	// "~"        : Matches regular expression, case sensitive.
	// "contains" : Contains the following values.
	// "wildcard" : Perform wildcard search.
	if strings.HasPrefix(statement, "Filename") || strings.HasPrefix(statement, "filename") {
		if strings.Contains(statement, "=") {
			parts := strings.Split(statement, "=")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "match", "filename", data)

		} else if strings.Contains(statement, "contains") {
			parts := strings.Split(statement, "contains")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			dataSlice := strings.Split(data, ",")
			for i, datum := range dataSlice {
				dataSlice[i] = `"` + strings.TrimSpace(datum) + `"`
			}

			return fmt.Sprintf(`{type: "%v", field: "%v", values: "%v"}`, "contains", "filename", strings.Join(dataSlice, `,`))

		} else if strings.Contains(statement, "wildcard") {
			parts := strings.Split(statement, "wildcard")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "wildcard", "filename", data)

		} else if strings.Contains(statement, "^") {
			parts := strings.Split(statement, "^")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "prefix", "filename", data)

		} else if strings.Contains(statement, "~") {
			parts := strings.Split(statement, "~")

			data := parts[len(parts)-1]
			data = strings.TrimSpace(data)
			data = libstring.StripChars(data, `"'`)

			return fmt.Sprintf(`{type: "%v", field: "%v", value: "%v"}`, "regexp", "filename", data)
		}
	}

	// Querying logline.
	// Operators:
	// "search" : Full text search.
	if strings.HasPrefix(statement, "Logline") || strings.HasPrefix(statement, "logline") {
		if strings.Contains(statement, "search") {
			return parseFullTextSearchField(statement, "logline", "search")
		}
	}

	return ""
}
<|endoftext|>


<commit_before>package main

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

	"github.com/NebulousLabs/Sia/network"
)

// TODO: timeouts?
func (d *daemon) handle(addr string) {
	// Web Interface
	http.HandleFunc("/", d.webIndex)
	http.Handle("/lib/", http.StripPrefix("/lib/", http.FileServer(http.Dir(d.styleDir))))

	// Host API Calls
	//
	// TODO: SetConfig also calls announce(), there should be smarter ways to
	// handle this.
	http.HandleFunc("/host/config", d.hostConfigHandler)
	http.HandleFunc("/host/setconfig", d.hostSetConfigHandler)

	// Miner API Calls
	http.HandleFunc("/miner/start", d.minerStartHandler)
	http.HandleFunc("/miner/status", d.minerStatusHandler)
	http.HandleFunc("/miner/stop", d.minerStopHandler)

	// Wallet API Calls
	http.HandleFunc("/wallet/address", d.walletAddressHandler)
	http.HandleFunc("/wallet/send", d.walletSendHandler)
	http.HandleFunc("/wallet/status", d.walletStatusHandler)

	// File API Calls
	http.HandleFunc("file/upload", d.fileUploadHandler)
	http.HandleFunc("file/download", d.fileDownloadHandler)
	http.HandleFunc("file/status", d.fileStatusHandler)

	// Misc. API Calls
	http.HandleFunc("/sync", d.syncHandler)
	http.HandleFunc("/peer/add", d.peerAddHandler)
	http.HandleFunc("/peer/remove", d.peerRemoveHandler)
	http.HandleFunc("/status", d.statusHandler)
	http.HandleFunc("/update/check", d.updateCheckHandler)
	http.HandleFunc("/update/apply", d.updateApplyHandler)
	http.HandleFunc("/stop", d.stopHandler)

	http.ListenAndServe(addr, nil)
}

// writeJSON writes the object to the ResponseWriter. If the encoding fails, an
// error is written instead.
func writeJSON(w http.ResponseWriter, obj interface{}) {
	if json.NewEncoder(w).Encode(obj) != nil {
		http.Error(w, "Failed to encode response", 500)
	}
}

// writeSuccess writes the success json object ({"Success":true}) to the
// ResponseWriter
func writeSuccess(w http.ResponseWriter) {
	writeJSON(w, struct {
		Success bool
	}{true})
}

func (d *daemon) statusHandler(w http.ResponseWriter, req *http.Request) {
	writeJSON(w, d.core.StateInfo())
}

func (d *daemon) stopHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: more graceful shutdown?
	d.core.Close()
	os.Exit(0)
}

func (d *daemon) syncHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: don't spawn multiple CatchUps
	if len(d.core.AddressBook()) == 0 {
		http.Error(w, "No peers available for syncing", 500)
		return
	}

	go d.core.CatchUp(d.core.RandomPeer())

	writeSuccess(w)
}

func (d *daemon) peerAddHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: this should return an error
	d.core.AddPeer(network.Address(req.FormValue("addr")))

	writeSuccess(w)
}

func (d *daemon) peerRemoveHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: this should return an error
	d.core.RemovePeer(network.Address(req.FormValue("addr")))

	writeSuccess(w)
}

func (d *daemon) updateCheckHandler(w http.ResponseWriter, req *http.Request) {
	available, version, err := d.checkForUpdate()
	if err != nil {
		http.Error(w, err.Error(), 500)
	} else {
		writeJSON(w, struct {
			Available bool
			Version   string
		}{available, version})
	}
}

func (d *daemon) updateApplyHandler(w http.ResponseWriter, req *http.Request) {
	err := d.applyUpdate(req.FormValue("version"))
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	writeSuccess(w)
}
<commit_msg>error styling<commit_after>package main

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

	"github.com/NebulousLabs/Sia/network"
)

// TODO: timeouts?
func (d *daemon) handle(addr string) {
	// Web Interface
	http.HandleFunc("/", d.webIndex)
	http.Handle("/lib/", http.StripPrefix("/lib/", http.FileServer(http.Dir(d.styleDir))))

	// Host API Calls
	//
	// TODO: SetConfig also calls announce(), there should be smarter ways to
	// handle this.
	http.HandleFunc("/host/config", d.hostConfigHandler)
	http.HandleFunc("/host/setconfig", d.hostSetConfigHandler)

	// Miner API Calls
	http.HandleFunc("/miner/start", d.minerStartHandler)
	http.HandleFunc("/miner/status", d.minerStatusHandler)
	http.HandleFunc("/miner/stop", d.minerStopHandler)

	// Wallet API Calls
	http.HandleFunc("/wallet/address", d.walletAddressHandler)
	http.HandleFunc("/wallet/send", d.walletSendHandler)
	http.HandleFunc("/wallet/status", d.walletStatusHandler)

	// File API Calls
	http.HandleFunc("file/upload", d.fileUploadHandler)
	http.HandleFunc("file/download", d.fileDownloadHandler)
	http.HandleFunc("file/status", d.fileStatusHandler)

	// Misc. API Calls
	http.HandleFunc("/sync", d.syncHandler)
	http.HandleFunc("/peer/add", d.peerAddHandler)
	http.HandleFunc("/peer/remove", d.peerRemoveHandler)
	http.HandleFunc("/status", d.statusHandler)
	http.HandleFunc("/update/check", d.updateCheckHandler)
	http.HandleFunc("/update/apply", d.updateApplyHandler)
	http.HandleFunc("/stop", d.stopHandler)

	http.ListenAndServe(addr, nil)
}

// writeJSON writes the object to the ResponseWriter. If the encoding fails, an
// error is written instead.
func writeJSON(w http.ResponseWriter, obj interface{}) {
	if json.NewEncoder(w).Encode(obj) != nil {
		http.Error(w, "Failed to encode response", 500)
	}
}

// writeSuccess writes the success json object ({"Success":true}) to the
// ResponseWriter
func writeSuccess(w http.ResponseWriter) {
	writeJSON(w, struct {
		Success bool
	}{true})
}

func (d *daemon) statusHandler(w http.ResponseWriter, req *http.Request) {
	writeJSON(w, d.core.StateInfo())
}

func (d *daemon) stopHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: more graceful shutdown?
	d.core.Close()
	os.Exit(0)
}

func (d *daemon) syncHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: don't spawn multiple CatchUps
	if len(d.core.AddressBook()) == 0 {
		http.Error(w, "No peers available for syncing", 500)
		return
	}

	go d.core.CatchUp(d.core.RandomPeer())

	writeSuccess(w)
}

func (d *daemon) peerAddHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: this should return an error
	d.core.AddPeer(network.Address(req.FormValue("addr")))

	writeSuccess(w)
}

func (d *daemon) peerRemoveHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: this should return an error
	d.core.RemovePeer(network.Address(req.FormValue("addr")))

	writeSuccess(w)
}

func (d *daemon) updateCheckHandler(w http.ResponseWriter, req *http.Request) {
	available, version, err := d.checkForUpdate()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	writeJSON(w, struct {
		Available bool
		Version   string
	}{available, version})
}

func (d *daemon) updateApplyHandler(w http.ResponseWriter, req *http.Request) {
	err := d.applyUpdate(req.FormValue("version"))
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	writeSuccess(w)
}
<|endoftext|>


<commit_before>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dashboard

// This file handles operations on the CL entity kind.

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strings"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/mail"
	"appengine/taskqueue"
	"appengine/urlfetch"
	"appengine/user"
)

func init() {
	http.HandleFunc("/assign", handleAssign)
	http.HandleFunc("/update-cl", handleUpdateCL)
}

const codereviewBase = "http://codereview.appspot.com"

var clRegexp = regexp.MustCompile(`\d+`)

// CL represents a code review.
type CL struct {
	Number string // e.g. "5903061"
	Closed bool
	Owner  string // email address

	Created, Modified time.Time

	Description []byte `datastore:",noindex"`
	FirstLine   string `datastore:",noindex"`
	LGTMs       []string

	// Mail information.
	Subject       string   `datastore:",noindex"`
	Recipients    []string `datastore:",noindex"`
	LastMessageID string   `datastore:",noindex"`

	// These are person IDs (e.g. "rsc"); they may be empty
	Author   string
	Reviewer string
}

// DisplayOwner returns the CL's owner, either as their email address
// or the person ID if it's a reviewer. It is for display only.
func (cl *CL) DisplayOwner() string {
	if p, ok := emailToPerson[cl.Owner]; ok {
		return p
	}
	return cl.Owner
}

func (cl *CL) FirstLineHTML() template.HTML {
	s := template.HTMLEscapeString(cl.FirstLine)
	// Embolden the package name.
	if i := strings.Index(s, ":"); i >= 0 {
		s = "" + s[:i] + "" + s[i:]
	}
	return template.HTML(s)
}

func (cl *CL) LGTMHTML() template.HTML {
	x := make([]string, len(cl.LGTMs))
	for i, s := range cl.LGTMs {
		s = template.HTMLEscapeString(s)
		if !strings.Contains(s, "@") {
			s = "" + s + ""
		}
		s = `<span class="email">` + s + ""
		x[i] = s
	}
	return template.HTML(strings.Join(x, ", "))
}

func (cl *CL) ModifiedAgo() string {
	// Just the first non-zero unit.
	units := [...]struct {
		suffix string
		unit   time.Duration
	}{
		{"d", 24 * time.Hour},
		{"h", time.Hour},
		{"m", time.Minute},
		{"s", time.Second},
	}
	d := time.Now().Sub(cl.Modified)
	for _, u := range units {
		if d > u.unit {
			return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
		}
	}
	return "just now"
}

func handleAssign(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	if r.Method != "POST" {
		http.Error(w, "Bad method "+r.Method, 400)
		return
	}

	u := user.Current(c)
	if _, ok := emailToPerson[u.Email]; !ok {
		http.Error(w, "Not allowed", http.StatusUnauthorized)
		return
	}

	n, rev := r.FormValue("cl"), r.FormValue("r")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}
	if _, ok := preferredEmail[rev]; !ok && rev != "" {
		c.Errorf("Unknown reviewer %q", rev)
		http.Error(w, "Unknown reviewer", 400)
		return
	}

	key := datastore.NewKey(c, "CL", n, 0, nil)

	if rev != "" {
		// Make sure the reviewer is listed in Rietveld as a reviewer.
		url := codereviewBase + "/" + n + "/fields"
		resp, err := urlfetch.Client(c).Get(url + "?field=reviewers")
		if err != nil {
			c.Errorf("Retrieving CL reviewer list failed: %v", err)
			http.Error(w, err.Error(), 500)
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			c.Errorf("Retrieving CL reviewer list failed: got HTTP response %d", resp.StatusCode)
			http.Error(w, "Failed contacting Rietveld", 500)
			return
		}

		var apiResp struct {
			Reviewers []string `json:"reviewers"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
			// probably can't be retried
			msg := fmt.Sprintf("Malformed JSON from %v: %v", url, err)
			c.Errorf("%s", msg)
			http.Error(w, msg, 500)
			return
		}
		found := false
		for _, r := range apiResp.Reviewers {
			if emailToPerson[r] == rev {
				found = true
				break
			}
		}
		if !found {
			c.Infof("Adding %v as a reviewer of CL %v", rev, n)

			// We can't do this easily, as we need authentication to edit
			// an issue on behalf of a user, which is non-trivial. For now,
			// just send a mail with the body "R=<reviewer>", Cc'ing that person,
			// and rely on social convention.
			cl := new(CL)
			err := datastore.Get(c, key, cl)
			if err != nil {
				c.Errorf("%s", err)
				http.Error(w, err.Error(), 500)
				return
			}
			// The current data does not have the subject/recipient information.
			// TODO(dsymonds): Remove this if when all the CLs have subject lines.
			if cl.Subject != "" {
				msg := &mail.Message{
					Sender: u.Email,
					To:     []string{preferredEmail[rev]},
					Cc:     cl.Recipients,
					// Take care to match Rietveld's subject line
					// so that Gmail will correctly thread mail.
					Subject: cl.Subject + " (issue " + n + ")",
					Body:    "R=" + rev + "\n\n(sent by gocodereview)",
				}
				// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
				// when the appengine/mail package supports that.
				if err := mail.Send(c, msg); err != nil {
					c.Errorf("mail.Send: %v", err)
				}
			}
		}
	}

	// Update our own record.
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		cl := new(CL)
		err := datastore.Get(c, key, cl)
		if err != nil {
			return err
		}
		cl.Reviewer = rev
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		msg := fmt.Sprintf("Assignment failed: %v", err)
		c.Errorf("%s", msg)
		http.Error(w, msg, 500)
		return
	}
	c.Infof("Assigned CL %v to %v", n, rev)
}

func UpdateCLLater(c appengine.Context, n string, delay time.Duration) {
	t := taskqueue.NewPOSTTask("/update-cl", url.Values{
		"cl": []string{n},
	})
	t.Delay = delay
	if _, err := taskqueue.Add(c, t, "update-cl"); err != nil {
		c.Errorf("Failed adding task: %v", err)
	}
}

func handleUpdateCL(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	n := r.FormValue("cl")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}

	if err := updateCL(c, n); err != nil {
		c.Errorf("Failed updating CL %v: %v", n, err)
		http.Error(w, "Failed update", 500)
		return
	}

	io.WriteString(w, "OK")
}

// updateCL updates a single CL. If a retryable failure occurs, an error is returned.
func updateCL(c appengine.Context, n string) error {
	c.Debugf("Updating CL %v", n)

	url := codereviewBase + "/api/" + n + "?messages=true"
	resp, err := urlfetch.Client(c).Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return fmt.Errorf("Update: got HTTP response %d", resp.StatusCode)
	}

	var apiResp struct {
		Description string `json:"description"`
		Created     string `json:"created"`
		OwnerEmail  string `json:"owner_email"`
		Modified    string `json:"modified"`
		Closed      bool   `json:"closed"`
		Subject     string `json:"subject"`
		Messages    []struct {
			Text       string   `json:"text"`
			Sender     string   `json:"sender"`
			Recipients []string `json:"recipients"`
			Approval   bool     `json:"approval"`
		} `json:"messages"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
		// probably can't be retried
		c.Errorf("Malformed JSON from %v: %v", url, err)
		return nil
	}
	//c.Infof("RAW: %+v", apiResp)

	cl := &CL{
		Number:      n,
		Closed:      apiResp.Closed,
		Owner:       apiResp.OwnerEmail,
		Description: []byte(apiResp.Description),
		FirstLine:   apiResp.Description,
		Subject:     apiResp.Subject,
		Author:      emailToPerson[apiResp.OwnerEmail],
	}
	cl.Created, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Created)
	if err != nil {
		c.Errorf("Bad creation time %q: %v", apiResp.Created, err)
	}
	cl.Modified, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Modified)
	if err != nil {
		c.Errorf("Bad modification time %q: %v", apiResp.Modified, err)
	}
	if i := strings.Index(cl.FirstLine, "\n"); i >= 0 {
		cl.FirstLine = cl.FirstLine[:i]
	}
	lgtm := make(map[string]bool)
	rcpt := make(map[string]bool)
	for _, msg := range apiResp.Messages {
		s, rev := msg.Sender, false
		if p, ok := emailToPerson[s]; ok {
			s, rev = p, true
		}

		// CLs submitted by someone other than the CL owner do not immediately
		// transition to "closed". Let's simulate the intention by treating
		// messages starting with "*** Submitted as " from a reviewer as a
		// signal that the CL is now closed.
		if rev && strings.HasPrefix(msg.Text, "*** Submitted as ") {
			cl.Closed = true
		}

		if msg.Approval {
			lgtm[s] = true
		}

		for _, r := range msg.Recipients {
			rcpt[r] = true
		}
	}
	for l := range lgtm {
		cl.LGTMs = append(cl.LGTMs, l)
	}
	for r := range rcpt {
		cl.Recipients = append(cl.Recipients, r)
	}
	sort.Strings(cl.LGTMs)
	sort.Strings(cl.Recipients)

	key := datastore.NewKey(c, "CL", n, 0, nil)
	err = datastore.RunInTransaction(c, func(c appengine.Context) error {
		ocl := new(CL)
		err := datastore.Get(c, key, ocl)
		if err != nil && err != datastore.ErrNoSuchEntity {
			return err
		} else if err == nil {
			// LastMessageID and Reviewer need preserving.
			cl.LastMessageID = ocl.LastMessageID
			cl.Reviewer = ocl.Reviewer
		}
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		return err
	}
	c.Infof("Updated CL %v", n)
	return nil
}
<commit_msg>misc/dashboard/codereview: remove transitional code.<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dashboard

// This file handles operations on the CL entity kind.

import (
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strings"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/mail"
	"appengine/taskqueue"
	"appengine/urlfetch"
	"appengine/user"
)

func init() {
	http.HandleFunc("/assign", handleAssign)
	http.HandleFunc("/update-cl", handleUpdateCL)
}

const codereviewBase = "http://codereview.appspot.com"

var clRegexp = regexp.MustCompile(`\d+`)

// CL represents a code review.
type CL struct {
	Number string // e.g. "5903061"
	Closed bool
	Owner  string // email address

	Created, Modified time.Time

	Description []byte `datastore:",noindex"`
	FirstLine   string `datastore:",noindex"`
	LGTMs       []string

	// Mail information.
	Subject       string   `datastore:",noindex"`
	Recipients    []string `datastore:",noindex"`
	LastMessageID string   `datastore:",noindex"`

	// These are person IDs (e.g. "rsc"); they may be empty
	Author   string
	Reviewer string
}

// DisplayOwner returns the CL's owner, either as their email address
// or the person ID if it's a reviewer. It is for display only.
func (cl *CL) DisplayOwner() string {
	if p, ok := emailToPerson[cl.Owner]; ok {
		return p
	}
	return cl.Owner
}

func (cl *CL) FirstLineHTML() template.HTML {
	s := template.HTMLEscapeString(cl.FirstLine)
	// Embolden the package name.
	if i := strings.Index(s, ":"); i >= 0 {
		s = "" + s[:i] + "" + s[i:]
	}
	return template.HTML(s)
}

func (cl *CL) LGTMHTML() template.HTML {
	x := make([]string, len(cl.LGTMs))
	for i, s := range cl.LGTMs {
		s = template.HTMLEscapeString(s)
		if !strings.Contains(s, "@") {
			s = "" + s + ""
		}
		s = `<span class="email">` + s + ""
		x[i] = s
	}
	return template.HTML(strings.Join(x, ", "))
}

func (cl *CL) ModifiedAgo() string {
	// Just the first non-zero unit.
	units := [...]struct {
		suffix string
		unit   time.Duration
	}{
		{"d", 24 * time.Hour},
		{"h", time.Hour},
		{"m", time.Minute},
		{"s", time.Second},
	}
	d := time.Now().Sub(cl.Modified)
	for _, u := range units {
		if d > u.unit {
			return fmt.Sprintf("%d%s", d/u.unit, u.suffix)
		}
	}
	return "just now"
}

func handleAssign(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	if r.Method != "POST" {
		http.Error(w, "Bad method "+r.Method, 400)
		return
	}

	u := user.Current(c)
	if _, ok := emailToPerson[u.Email]; !ok {
		http.Error(w, "Not allowed", http.StatusUnauthorized)
		return
	}

	n, rev := r.FormValue("cl"), r.FormValue("r")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}
	if _, ok := preferredEmail[rev]; !ok && rev != "" {
		c.Errorf("Unknown reviewer %q", rev)
		http.Error(w, "Unknown reviewer", 400)
		return
	}

	key := datastore.NewKey(c, "CL", n, 0, nil)

	if rev != "" {
		// Make sure the reviewer is listed in Rietveld as a reviewer.
		url := codereviewBase + "/" + n + "/fields"
		resp, err := urlfetch.Client(c).Get(url + "?field=reviewers")
		if err != nil {
			c.Errorf("Retrieving CL reviewer list failed: %v", err)
			http.Error(w, err.Error(), 500)
			return
		}
		defer resp.Body.Close()
		if resp.StatusCode != 200 {
			c.Errorf("Retrieving CL reviewer list failed: got HTTP response %d", resp.StatusCode)
			http.Error(w, "Failed contacting Rietveld", 500)
			return
		}

		var apiResp struct {
			Reviewers []string `json:"reviewers"`
		}
		if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
			// probably can't be retried
			msg := fmt.Sprintf("Malformed JSON from %v: %v", url, err)
			c.Errorf("%s", msg)
			http.Error(w, msg, 500)
			return
		}
		found := false
		for _, r := range apiResp.Reviewers {
			if emailToPerson[r] == rev {
				found = true
				break
			}
		}
		if !found {
			c.Infof("Adding %v as a reviewer of CL %v", rev, n)

			// We can't do this easily, as we need authentication to edit
			// an issue on behalf of a user, which is non-trivial. For now,
			// just send a mail with the body "R=<reviewer>", Cc'ing that person,
			// and rely on social convention.
			cl := new(CL)
			err := datastore.Get(c, key, cl)
			if err != nil {
				c.Errorf("%s", err)
				http.Error(w, err.Error(), 500)
				return
			}
			msg := &mail.Message{
				Sender: u.Email,
				To:     []string{preferredEmail[rev]},
				Cc:     cl.Recipients,
				// Take care to match Rietveld's subject line
				// so that Gmail will correctly thread mail.
				Subject: cl.Subject + " (issue " + n + ")",
				Body:    "R=" + rev + "\n\n(sent by gocodereview)",
			}
			// TODO(dsymonds): Use cl.LastMessageID as the In-Reply-To header
			// when the appengine/mail package supports that.
			if err := mail.Send(c, msg); err != nil {
				c.Errorf("mail.Send: %v", err)
			}
		}
	}

	// Update our own record.
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		cl := new(CL)
		err := datastore.Get(c, key, cl)
		if err != nil {
			return err
		}
		cl.Reviewer = rev
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		msg := fmt.Sprintf("Assignment failed: %v", err)
		c.Errorf("%s", msg)
		http.Error(w, msg, 500)
		return
	}
	c.Infof("Assigned CL %v to %v", n, rev)
}

func UpdateCLLater(c appengine.Context, n string, delay time.Duration) {
	t := taskqueue.NewPOSTTask("/update-cl", url.Values{
		"cl": []string{n},
	})
	t.Delay = delay
	if _, err := taskqueue.Add(c, t, "update-cl"); err != nil {
		c.Errorf("Failed adding task: %v", err)
	}
}

func handleUpdateCL(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)

	n := r.FormValue("cl")
	if !clRegexp.MatchString(n) {
		c.Errorf("Bad CL %q", n)
		http.Error(w, "Bad CL", 400)
		return
	}

	if err := updateCL(c, n); err != nil {
		c.Errorf("Failed updating CL %v: %v", n, err)
		http.Error(w, "Failed update", 500)
		return
	}

	io.WriteString(w, "OK")
}

// updateCL updates a single CL. If a retryable failure occurs, an error is returned.
func updateCL(c appengine.Context, n string) error {
	c.Debugf("Updating CL %v", n)

	url := codereviewBase + "/api/" + n + "?messages=true"
	resp, err := urlfetch.Client(c).Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return fmt.Errorf("Update: got HTTP response %d", resp.StatusCode)
	}

	var apiResp struct {
		Description string `json:"description"`
		Created     string `json:"created"`
		OwnerEmail  string `json:"owner_email"`
		Modified    string `json:"modified"`
		Closed      bool   `json:"closed"`
		Subject     string `json:"subject"`
		Messages    []struct {
			Text       string   `json:"text"`
			Sender     string   `json:"sender"`
			Recipients []string `json:"recipients"`
			Approval   bool     `json:"approval"`
		} `json:"messages"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
		// probably can't be retried
		c.Errorf("Malformed JSON from %v: %v", url, err)
		return nil
	}
	//c.Infof("RAW: %+v", apiResp)

	cl := &CL{
		Number:      n,
		Closed:      apiResp.Closed,
		Owner:       apiResp.OwnerEmail,
		Description: []byte(apiResp.Description),
		FirstLine:   apiResp.Description,
		Subject:     apiResp.Subject,
		Author:      emailToPerson[apiResp.OwnerEmail],
	}
	cl.Created, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Created)
	if err != nil {
		c.Errorf("Bad creation time %q: %v", apiResp.Created, err)
	}
	cl.Modified, err = time.Parse("2006-01-02 15:04:05.000000", apiResp.Modified)
	if err != nil {
		c.Errorf("Bad modification time %q: %v", apiResp.Modified, err)
	}
	if i := strings.Index(cl.FirstLine, "\n"); i >= 0 {
		cl.FirstLine = cl.FirstLine[:i]
	}
	lgtm := make(map[string]bool)
	rcpt := make(map[string]bool)
	for _, msg := range apiResp.Messages {
		s, rev := msg.Sender, false
		if p, ok := emailToPerson[s]; ok {
			s, rev = p, true
		}

		// CLs submitted by someone other than the CL owner do not immediately
		// transition to "closed". Let's simulate the intention by treating
		// messages starting with "*** Submitted as " from a reviewer as a
		// signal that the CL is now closed.
		if rev && strings.HasPrefix(msg.Text, "*** Submitted as ") {
			cl.Closed = true
		}

		if msg.Approval {
			lgtm[s] = true
		}

		for _, r := range msg.Recipients {
			rcpt[r] = true
		}
	}
	for l := range lgtm {
		cl.LGTMs = append(cl.LGTMs, l)
	}
	for r := range rcpt {
		cl.Recipients = append(cl.Recipients, r)
	}
	sort.Strings(cl.LGTMs)
	sort.Strings(cl.Recipients)

	key := datastore.NewKey(c, "CL", n, 0, nil)
	err = datastore.RunInTransaction(c, func(c appengine.Context) error {
		ocl := new(CL)
		err := datastore.Get(c, key, ocl)
		if err != nil && err != datastore.ErrNoSuchEntity {
			return err
		} else if err == nil {
			// LastMessageID and Reviewer need preserving.
			cl.LastMessageID = ocl.LastMessageID
			cl.Reviewer = ocl.Reviewer
		}
		_, err = datastore.Put(c, key, cl)
		return err
	}, nil)
	if err != nil {
		return err
	}
	c.Infof("Updated CL %v", n)
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tensorflow

// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
// #include "tensorflow/c/eager/c_api.h"
import "C"
import (
	"runtime"
	"unsafe"
)

// TensorHandle is a handle to a tensor on a device.
//
// A Tensor referenced by a TensorHandle may be on any device, whereas a Tensor
// always resides in the host CPU's memory.
//
// A Tensor referenced by a TensorHandle may not have been computed yet. For
// example, a TensorHandle might reference the output of an operation that has
// not finished executing. Because of this, various methods, such as Shape() may
// block until the tensor has been instantiated.
//
// This allows multiple operations to be performed on tensors on a device
// (e.g. a GPU) without sending these values back to the host CPU in between
// every operation.
type TensorHandle struct {
	c *C.TFE_TensorHandle
}

// NewTensorHandle creates a new tensor handle from a tensor.
func NewTensorHandle(t *Tensor) (*TensorHandle, error) {
	status := newStatus()
	cHandle := C.TFE_NewTensorHandle(t.c, status.c)
	if err := status.Err(); err != nil {
		return nil, err
	}

	th := &TensorHandle{c: cHandle}
	runtime.SetFinalizer(th, (*TensorHandle).finalizer)
	return th, nil
}

func (th *TensorHandle) finalizer() {
	C.TFE_DeleteTensorHandle(th.c)
}

// DataType returns the TensorHandle's datatype.
func (th *TensorHandle) DataType() DataType {
	return DataType(C.TFE_TensorHandleDataType(th.c))
}

// Shape returns the shape of the Tensor referenced by th.
func (th *TensorHandle) Shape() ([]int64, error) {
	n, err := th.numDims()
	if err != nil {
		return nil, err
	}
	r := make([]int64, n)
	for i := 0; i < n; i++ {
		if r[i], err = th.dim(i); err != nil {
			return nil, err
		}
	}
	return r, nil
}

// numDims returns the number of dimensions of the TensorHandle. It blocks
// until the operation that produces the handle has completed.
func (th *TensorHandle) numDims() (int, error) {
	status := newStatus()
	n := int(C.TFE_TensorHandleNumDims(th.c, status.c))
	return n, status.Err()
}

// dim returns the size of the index'th dimension of the TensorHandle. It
// blocks until the operation that produces the handle has completed.
func (th *TensorHandle) dim(index int) (int64, error) {
	status := newStatus()
	n := int64(C.TFE_TensorHandleDim(th.c, C.int(index), status.c))
	if err := status.Err(); err != nil {
		return 0, err
	}
	return n, nil
}

// DeviceName returns the name of the device of the operation that produced the
// TensorHandle. If the handle was produced by a copy, it returns the
// destination device of the copy. Note that returned device name is not always
// the device holding the tensor handle's memory. If you want the latter, use
// BackingDeviceName. This function will block till the operation that produces
// th has completed.
func (th *TensorHandle) DeviceName() (string, error) {
	status := newStatus()
	name := C.TFE_TensorHandleDeviceName(th.c, status.c)
	if err := status.Err(); err != nil {
		return "", err
	}
	return C.GoString(name), nil
}

// BackingDeviceName returns the name of the device in whose memory the tensor
// handle resides. This function will block till the operation that produces
// `h` has completed.
func (th *TensorHandle) BackingDeviceName() (string, error) {
	status := newStatus()
	name := C.TFE_TensorHandleBackingDeviceName(th.c, status.c)
	if err := status.Err(); err != nil {
		return "", err
	}
	return C.GoString(name), nil
}

// ToTensor returns the Tensor referenced by th. It may block if this tensor is
// not yet computed.
func (th *TensorHandle) ToTensor() (*Tensor, error) {
	status := newStatus()
	cTensor := C.TFE_TensorHandleResolve(th.c, status.c)
	if err := status.Err(); err != nil {
		return nil, err
	}
	return newTensorFromC(cTensor), nil
}

// CopyToDevice creates a new TensorHandle with the same contents as this
// TensorHandle but placed in the memory of the device 'deviceName'. If source
// and destination are the same device, then this creates a new handle that
// shares the underlying buffer. Otherwise, it currently requires at least one
// of the source or destination devices to be CPU (i.e., for the source or
// destination tensor to be placed in host memory).
func (th *TensorHandle) CopyToDevice(c *Context, deviceName string) (*TensorHandle, error) {
	status := newStatus()
	n := C.CString(deviceName)
	newTh := C.TFE_TensorHandleCopyToDevice(th.c, c.c, n, status.c)
	C.free(unsafe.Pointer(n))
	if err := status.Err(); err != nil {
		return nil, err
	}
	return &TensorHandle{c: newTh}, nil
}
<commit_msg>Fix memory leak in Go API.<commit_after>/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tensorflow

// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
// #include "tensorflow/c/eager/c_api.h"
import "C"
import (
	"runtime"
	"unsafe"
)

// TensorHandle is a handle to a tensor on a device.
//
// A Tensor referenced by a TensorHandle may be on any device, whereas a Tensor
// always resides in the host CPU's memory.
//
// A Tensor referenced by a TensorHandle may not have been computed yet. For
// example, a TensorHandle might reference the output of an operation that has
// not finished executing. Because of this, various methods, such as Shape() may
// block until the tensor has been instantiated.
//
// This allows multiple operations to be performed on tensors on a device
// (e.g. a GPU) without sending these values back to the host CPU in between
// every operation.
type TensorHandle struct {
	c *C.TFE_TensorHandle
}

// NewTensorHandle creates a new tensor handle from a tensor.
func NewTensorHandle(t *Tensor) (*TensorHandle, error) {
	status := newStatus()
	cHandle := C.TFE_NewTensorHandle(t.c, status.c)
	if err := status.Err(); err != nil {
		return nil, err
	}

	th := &TensorHandle{c: cHandle}
	runtime.SetFinalizer(th, (*TensorHandle).finalizer)
	return th, nil
}

func (th *TensorHandle) finalizer() {
	C.TFE_DeleteTensorHandle(th.c)
}

// newTensorHandleFromC takes ownership of c and returns the owning TensorHandle.
func newTensorHandleFromC(c *C.TFE_TensorHandle) *TensorHandle {
	th := &TensorHandle{c: c}
	runtime.SetFinalizer(th, (*TensorHandle).finalizer)
	return th
}

// DataType returns the TensorHandle's datatype.
func (th *TensorHandle) DataType() DataType {
	return DataType(C.TFE_TensorHandleDataType(th.c))
}

// Shape returns the shape of the Tensor referenced by th.
func (th *TensorHandle) Shape() ([]int64, error) {
	n, err := th.numDims()
	if err != nil {
		return nil, err
	}
	r := make([]int64, n)
	for i := 0; i < n; i++ {
		if r[i], err = th.dim(i); err != nil {
			return nil, err
		}
	}
	return r, nil
}

// numDims returns the number of dimensions of the TensorHandle. It blocks
// until the operation that produces the handle has completed.
func (th *TensorHandle) numDims() (int, error) {
	status := newStatus()
	n := int(C.TFE_TensorHandleNumDims(th.c, status.c))
	return n, status.Err()
}

// dim returns the size of the index'th dimension of the TensorHandle. It
// blocks until the operation that produces the handle has completed.
func (th *TensorHandle) dim(index int) (int64, error) {
	status := newStatus()
	n := int64(C.TFE_TensorHandleDim(th.c, C.int(index), status.c))
	if err := status.Err(); err != nil {
		return 0, err
	}
	return n, nil
}

// DeviceName returns the name of the device of the operation that produced the
// TensorHandle. If the handle was produced by a copy, it returns the
// destination device of the copy. Note that returned device name is not always
// the device holding the tensor handle's memory. If you want the latter, use
// BackingDeviceName. This function will block till the operation that produces
// th has completed.
func (th *TensorHandle) DeviceName() (string, error) {
	status := newStatus()
	name := C.TFE_TensorHandleDeviceName(th.c, status.c)
	if err := status.Err(); err != nil {
		return "", err
	}
	return C.GoString(name), nil
}

// BackingDeviceName returns the name of the device in whose memory the tensor
// handle resides. This function will block till the operation that produces
// `h` has completed.
func (th *TensorHandle) BackingDeviceName() (string, error) {
	status := newStatus()
	name := C.TFE_TensorHandleBackingDeviceName(th.c, status.c)
	if err := status.Err(); err != nil {
		return "", err
	}
	return C.GoString(name), nil
}

// ToTensor returns the Tensor referenced by th. It may block if this tensor is
// not yet computed.
func (th *TensorHandle) ToTensor() (*Tensor, error) {
	status := newStatus()
	cTensor := C.TFE_TensorHandleResolve(th.c, status.c)
	if err := status.Err(); err != nil {
		return nil, err
	}
	return newTensorFromC(cTensor), nil
}

// CopyToDevice creates a new TensorHandle with the same contents as this
// TensorHandle but placed in the memory of the device 'deviceName'. If source
// and destination are the same device, then this creates a new handle that
// shares the underlying buffer. Otherwise, it currently requires at least one
// of the source or destination devices to be CPU (i.e., for the source or
// destination tensor to be placed in host memory).
func (th *TensorHandle) CopyToDevice(c *Context, deviceName string) (*TensorHandle, error) {
	status := newStatus()
	n := C.CString(deviceName)
	newTh := C.TFE_TensorHandleCopyToDevice(th.c, c.c, n, status.c)
	C.free(unsafe.Pointer(n))
	if err := status.Err(); err != nil {
		return nil, err
	}
	return newTensorHandleFromC(newTh), nil
}
<|endoftext|>


<commit_before>package run

import (
	"encoding/binary"
)

// some of these are also defined in defs.h, run.js and work.js

const (
	RODataAddr = 0x10000
)

const (
	abiVersion    = 0
	maxPacketSize = 0x10000
	maxServices   = 100

	headerSize = 8
)

var (
	endian = binary.LittleEndian
)
<commit_msg>run: note about pipe buffer size<commit_after>package run

import (
	"encoding/binary"
)

// some of these are also defined in defs.h, run.js and work.js

const (
	RODataAddr = 0x10000
)

const (
	abiVersion    = 0
	maxPacketSize = 0x10000 // coincides with default pipe buffer size on Linux
	maxServices   = 100

	headerSize = 8
)

var (
	endian = binary.LittleEndian
)
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
)

type SlackAPI struct {
	Owner          Owner
	RequestParams  []string
	RobotImage     string
	RobotImageType string
	RobotIsActive  bool
	RobotName      string
	TeamChannels   ResponseChannelsList
	TeamGroups     ResponseGroupsList
	TeamUsers      ResponseUsersList
	Token          string
}

func (s *SlackAPI) SetToken(token string) {
	s.Token = token
}

func (s *SlackAPI) AutoConfigure() {
	s.Token = os.Getenv("SLACK_TOKEN")

	s.RobotName = "bender"
	s.RobotImage = ":robot_face:"
	s.RobotImageType = "emoji"
	s.RobotIsActive = false
}

func (s *SlackAPI) System(kommand string) []byte {
	var binary string
	var parts []string
	var arguments []string

	if kommand == "" {
		s.ReportError(errors.New("invalid empty command"))
	}

	parts = strings.Fields(kommand)
	binary = parts[0]
	arguments = parts[1:len(parts)]

	response, err := exec.Command(binary, arguments...).Output()

	if err != nil {
		s.ReportError(err)
	}

	return bytes.Trim(response, "\n")
}

func (s *SlackAPI) Url(action string, params []string) string {
	data := url.Values{}
	var parts []string
	var encoded string
	var url string = fmt.Sprintf("https://slack.com/api/%s", action)

	for _, keyvalue := range params {
		if keyvalue == "token" {
			keyvalue += "=" + s.Token
		}

		parts = strings.SplitN(keyvalue, "=", 2)

		if len(parts) == 2 {
			data.Add(parts[0], parts[1])
		}
	}

	encoded = data.Encode()

	if encoded != "" {
		url = fmt.Sprintf("%s?%s", url, encoded)
	}

	return url
}

func (s *SlackAPI) HttpRequest(method string, body io.Reader, action string, params []string) *http.Request {
	if len(s.RequestParams) > 0 {
		params = append(params, s.RequestParams...)
		s.RequestParams = s.RequestParams[:0]
	}

	var url string = s.Url(action, params)
	req, err := http.NewRequest(method, url, body)

	req.Header.Add("DNT", "1")
	req.Header.Add("Connection", "keep-alive")
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Accept-Language", "en-US,en")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("User-Agent", "Mozilla/5.0 (KHTML, like Gecko) Safari/537.36")

	if err != nil {
		s.ReportError(err)
	}

	return req
}

func (s *SlackAPI) AddRequestParam(param string, value string) {
	var parameter string = fmt.Sprintf("%s=%s", param, value)
	s.RequestParams = append(s.RequestParams, parameter)
}

func (s *SlackAPI) ExecuteRequest(req *http.Request, data interface{}) {
	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		s.ReportError(err)
	}

	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(&data)

	if err != nil {
		s.ReportError(err)
	}
}

func (s *SlackAPI) PrintCurlCommand(req *http.Request, params []string) {
	if os.Getenv("SLACK_VERBOSE") == "true" {
		fmt.Printf("curl -X %s \"%s\"", req.Method, req.URL)

		for header, values := range req.Header {
			fmt.Printf(" \x5c\n-H \"%s: %s\"", header, values[0])
		}

		fmt.Printf(" \x5c\n-H \"Host: %s\"", req.Host)

		for _, param := range params {
			if param == "token" {
				param = "token=" + s.Token
			}
			fmt.Printf(" \x5c\n-d \"%s\"", param)
		}

		fmt.Println()
	}
}

func (s *SlackAPI) GetRequest(data interface{}, action string, params ...string) {
	req := s.HttpRequest("GET", nil, action, params)
	s.PrintCurlCommand(req, params)
	s.ExecuteRequest(req, &data)
}

func (s *SlackAPI) PostRequest(data interface{}, action string, params ...string) {
	var parts []string
	var buffer bytes.Buffer
	writer := multipart.NewWriter(&buffer)

	// Append more HTTP request params.
	for _, keyvalue := range params {
		if keyvalue == "token" {
			keyvalue += "=" + s.Token
		}

		parts = strings.SplitN(keyvalue, "=", 2)

		/* Ignore empty parameters */
		if len(parts) != 2 {
			continue
		}

		/* Check if the parameter is referencing a file */
		isfile, fpath, fname := s.CheckFileReference(parts[1])

		if !isfile {
			fwriter, _ := writer.CreateFormField(parts[0])
			fwriter.Write([]byte(parts[1]))
			continue
		}

		if len(parts[1]) > 0 && parts[1][0] == '@' {
		}

		/* Read referenced file and attach to the request */
		resource, err := os.Open(fpath)
		if err != nil {
			s.ReportError(err)
		}
		defer resource.Close()
		fwriter, _ := writer.CreateFormFile(parts[0], fpath)
		io.Copy(fwriter, resource) /* attach file data */
		fwriter, _ = writer.CreateFormField("filename")
		fwriter.Write([]byte(fname))
	}

	writer.Close()

	// Now that you have a form, you can submit it to your handler.
	req := s.HttpRequest("POST", &buffer, action, nil)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	s.PrintCurlCommand(req, params)
	s.ExecuteRequest(req, &data)
}

func (s *SlackAPI) CheckFileReference(text string) (bool, string, string) {
	if len(text) < 2 || text[0] != '@' {
		return false, "", ""
	}

	fpath := text[1:len(text)]

	/* Check if the file actually exists */
	if _, err := os.Stat(fpath); os.IsNotExist(err) {
		return false, "", ""
	}

	parts := strings.Split(fpath, "/")
	fname := parts[len(parts)-1]

	return true, fpath, fname
}

func (s *SlackAPI) PrintFormattedJson(data interface{}) {
	response, err := json.MarshalIndent(data, "", "\x20\x20")

	if err != nil {
		s.ReportError(err)
	}

	fmt.Printf("%s\n", response)
}

func (s *SlackAPI) PrintInlineJson(data interface{}) {
	response, err := json.Marshal(data)

	if err != nil {
		s.ReportError(err)
	}

	fmt.Printf("%s\n", response)
}

func (s *SlackAPI) PrintAndExit(data interface{}) {
	s.PrintFormattedJson(data)
	os.Exit(0)
}

func (s *SlackAPI) ReportError(err error) {
	fmt.Printf("Error: %s\n", err)
	os.Exit(1)
}
<commit_msg>Fix JSON decoder when the response has a rate-limit<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
)

type SlackAPI struct {
	Owner          Owner
	RequestParams  []string
	RobotImage     string
	RobotImageType string
	RobotIsActive  bool
	RobotName      string
	TeamChannels   ResponseChannelsList
	TeamGroups     ResponseGroupsList
	TeamUsers      ResponseUsersList
	Token          string
}

func (s *SlackAPI) SetToken(token string) {
	s.Token = token
}

func (s *SlackAPI) AutoConfigure() {
	s.Token = os.Getenv("SLACK_TOKEN")

	s.RobotName = "bender"
	s.RobotImage = ":robot_face:"
	s.RobotImageType = "emoji"
	s.RobotIsActive = false
}

func (s *SlackAPI) System(kommand string) []byte {
	var binary string
	var parts []string
	var arguments []string

	if kommand == "" {
		s.ReportError(errors.New("invalid empty command"))
	}

	parts = strings.Fields(kommand)
	binary = parts[0]
	arguments = parts[1:len(parts)]

	response, err := exec.Command(binary, arguments...).Output()

	if err != nil {
		s.ReportError(err)
	}

	return bytes.Trim(response, "\n")
}

func (s *SlackAPI) Url(action string, params []string) string {
	data := url.Values{}
	var parts []string
	var encoded string
	var url string = fmt.Sprintf("https://slack.com/api/%s", action)

	for _, keyvalue := range params {
		if keyvalue == "token" {
			keyvalue += "=" + s.Token
		}

		parts = strings.SplitN(keyvalue, "=", 2)

		if len(parts) == 2 {
			data.Add(parts[0], parts[1])
		}
	}

	encoded = data.Encode()

	if encoded != "" {
		url = fmt.Sprintf("%s?%s", url, encoded)
	}

	return url
}

func (s *SlackAPI) HttpRequest(method string, body io.Reader, action string, params []string) *http.Request {
	if len(s.RequestParams) > 0 {
		params = append(params, s.RequestParams...)
		s.RequestParams = s.RequestParams[:0]
	}

	var url string = s.Url(action, params)
	req, err := http.NewRequest(method, url, body)

	req.Header.Add("DNT", "1")
	req.Header.Add("Connection", "keep-alive")
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Accept-Language", "en-US,en")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("User-Agent", "Mozilla/5.0 (KHTML, like Gecko) Safari/537.36")

	if err != nil {
		s.ReportError(err)
	}

	return req
}

func (s *SlackAPI) AddRequestParam(param string, value string) {
	var parameter string = fmt.Sprintf("%s=%s", param, value)
	s.RequestParams = append(s.RequestParams, parameter)
}

func (s *SlackAPI) ExecuteRequest(req *http.Request, data interface{}) {
	client := &http.Client{}
	resp, err := client.Do(req)

	if err != nil {
		s.ReportError(err)
	}

	defer resp.Body.Close()

	var buf bytes.Buffer
	tee := io.TeeReader(resp.Body, &buf)
	if err := json.NewDecoder(tee).Decode(&data); err != nil {
		out, _ := ioutil.ReadAll(&buf)
		if strings.Contains(string(out), "too many requests") {
			fake := "{\"ok\":false, \"error\":\"RATELIMIT\"}"
			read := bytes.NewReader([]byte(fake))
			json.NewDecoder(read).Decode(&data)
			return
		}
		s.ReportError(err)
	}
}

func (s *SlackAPI) PrintCurlCommand(req *http.Request, params []string) {
	if os.Getenv("SLACK_VERBOSE") == "true" {
		fmt.Printf("curl -X %s \"%s\"", req.Method, req.URL)

		for header, values := range req.Header {
			fmt.Printf(" \x5c\n-H \"%s: %s\"", header, values[0])
		}

		fmt.Printf(" \x5c\n-H \"Host: %s\"", req.Host)

		for _, param := range params {
			if param == "token" {
				param = "token=" + s.Token
			}
			fmt.Printf(" \x5c\n-d \"%s\"", param)
		}

		fmt.Println()
	}
}

func (s *SlackAPI) GetRequest(data interface{}, action string, params ...string) {
	req := s.HttpRequest("GET", nil, action, params)
	s.PrintCurlCommand(req, params)
	s.ExecuteRequest(req, &data)
}

func (s *SlackAPI) PostRequest(data interface{}, action string, params ...string) {
	var parts []string
	var buffer bytes.Buffer
	writer := multipart.NewWriter(&buffer)

	// Append more HTTP request params.
	for _, keyvalue := range params {
		if keyvalue == "token" {
			keyvalue += "=" + s.Token
		}

		parts = strings.SplitN(keyvalue, "=", 2)

		/* Ignore empty parameters */
		if len(parts) != 2 {
			continue
		}

		/* Check if the parameter is referencing a file */
		isfile, fpath, fname := s.CheckFileReference(parts[1])

		if !isfile {
			fwriter, _ := writer.CreateFormField(parts[0])
			fwriter.Write([]byte(parts[1]))
			continue
		}

		if len(parts[1]) > 0 && parts[1][0] == '@' {
		}

		/* Read referenced file and attach to the request */
		resource, err := os.Open(fpath)
		if err != nil {
			s.ReportError(err)
		}
		defer resource.Close()
		fwriter, _ := writer.CreateFormFile(parts[0], fpath)
		io.Copy(fwriter, resource) /* attach file data */
		fwriter, _ = writer.CreateFormField("filename")
		fwriter.Write([]byte(fname))
	}

	writer.Close()

	// Now that you have a form, you can submit it to your handler.
	req := s.HttpRequest("POST", &buffer, action, nil)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	s.PrintCurlCommand(req, params)
	s.ExecuteRequest(req, &data)
}

func (s *SlackAPI) CheckFileReference(text string) (bool, string, string) {
	if len(text) < 2 || text[0] != '@' {
		return false, "", ""
	}

	fpath := text[1:len(text)]

	/* Check if the file actually exists */
	if _, err := os.Stat(fpath); os.IsNotExist(err) {
		return false, "", ""
	}

	parts := strings.Split(fpath, "/")
	fname := parts[len(parts)-1]

	return true, fpath, fname
}

func (s *SlackAPI) PrintFormattedJson(data interface{}) {
	response, err := json.MarshalIndent(data, "", "\x20\x20")

	if err != nil {
		s.ReportError(err)
	}

	fmt.Printf("%s\n", response)
}

func (s *SlackAPI) PrintInlineJson(data interface{}) {
	response, err := json.Marshal(data)

	if err != nil {
		s.ReportError(err)
	}

	fmt.Printf("%s\n", response)
}

func (s *SlackAPI) PrintAndExit(data interface{}) {
	s.PrintFormattedJson(data)
	os.Exit(0)
}

func (s *SlackAPI) ReportError(err error) {
	fmt.Printf("Error: %s\n", err)
	os.Exit(1)
}
<|endoftext|>