<commit_before>package weed_server

import (
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"strconv"
	"strings"
	"time"

	"path"

	"bytes"

	"github.com/chrislusf/seaweedfs/go/glog"
	"github.com/chrislusf/seaweedfs/go/images"
	"github.com/chrislusf/seaweedfs/go/operation"
	"github.com/chrislusf/seaweedfs/go/storage"
	"github.com/chrislusf/seaweedfs/go/util"
)

var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
	n := new(storage.Needle)
	vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
	volumeId, err := storage.NewVolumeId(vid)
	if err != nil {
		glog.V(2).Infoln("parsing error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	err = n.ParsePath(fid)
	if err != nil {
		glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	glog.V(4).Infoln("volume", volumeId, "reading", n)
	if !vs.store.HasVolume(volumeId) {
		if !vs.ReadRedirect {
			glog.V(2).Infoln("volume is not local:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
			return
		}
		lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
		glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
		if err == nil && len(lookupResult.Locations) > 0 {
			http.Redirect(w, r, util.NormalizeUrl(lookupResult.Locations[0].PublicUrl)+r.URL.Path, http.StatusMovedPermanently)
		} else {
			glog.V(2).Infoln("lookup error:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
		}
		return
	}
	cookie := n.Cookie
	count, e := vs.store.ReadVolumeNeedle(volumeId, n)
	glog.V(4).Infoln("read bytes", count, "error", e)
	if e != nil || count <= 0 {
		glog.V(0).Infoln("read error:", e, r.URL.Path)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if n.Cookie != cookie {
		glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if n.LastModified != 0 {
		w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
		if r.Header.Get("If-Modified-Since") != "" {
			if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
				if t.Unix() >= int64(n.LastModified) {
					w.WriteHeader(http.StatusNotModified)
					return
				}
			}
		}
	}
	etag := n.Etag()
	if inm := r.Header.Get("If-None-Match"); inm == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}
	w.Header().Set("Etag", etag)

	if vs.tryHandleChunkedFile(n, filename, w, r) {
		return
	}

	if n.NameSize > 0 && filename == "" {
		filename = string(n.Name)
		if ext == "" {
			ext = path.Ext(filename)
		}
	}
	mtype := ""
	if n.MimeSize > 0 {
		mt := string(n.Mime)
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mtype = mt
		}
	}

	if ext != ".gz" {
		if n.IsGzipped() {
			if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
				w.Header().Set("Content-Encoding", "gzip")
			} else {
				if n.Data, err = operation.UnGzipData(n.Data); err != nil {
					glog.V(0).Infoln("ungzip error:", err, r.URL.Path)
				}
			}
		}
	}
	if ext == ".png" || ext == ".jpg" || ext == ".gif" {
		width, height := 0, 0
		if r.FormValue("width") != "" {
			width, _ = strconv.Atoi(r.FormValue("width"))
		}
		if r.FormValue("height") != "" {
			height, _ = strconv.Atoi(r.FormValue("height"))
		}
		n.Data, _, _ = images.Resized(ext, n.Data, width, height)
	}

	if e := writeResponseContent(filename, mtype, bytes.NewReader(n.Data), w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
}

func (vs *VolumeServer) tryHandleChunkedFile(n *storage.Needle, fileName string, w http.ResponseWriter, r *http.Request) (processed bool) {
	if !n.IsChunkedManifest() {
		return false
	}
	raw, _ := strconv.ParseBool(r.FormValue("raw"))
	if raw {
		return false
	}
	processed = true

	chunkManifest, e := operation.LoadChunkManifest(n.Data, n.IsGzipped())
	if e != nil {
		glog.V(0).Infof("Load chunked manifest (%s) error: %s", r.URL.Path, e.Error())
		return false
	}
	if fileName == "" && chunkManifest.Name != "" {
		fileName = chunkManifest.Name
	}
	mType := ""
	if chunkManifest.Mime != "" {
		mt := chunkManifest.Mime
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mType = mt
		}
	}

	w.Header().Set("X-File-Store", "chunked")

	chunkedFileReader := &operation.ChunkedFileReader{
		Manifest: chunkManifest,
		Master:   vs.GetMasterNode(),
	}
	defer chunkedFileReader.Close()
	if e := writeResponseContent(fileName, mType, chunkedFileReader, w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
	return
}

func writeResponseContent(filename, mimeType string, rs io.ReadSeeker, w http.ResponseWriter, r *http.Request) error {
	totalSize, e := rs.Seek(0, 2)
	if mimeType == "" {
		if ext := path.Ext(filename); ext != "" {
			mimeType = mime.TypeByExtension(ext)
		}
	}
	if mimeType != "" {
		w.Header().Set("Content-Type", mimeType)
	}
	if filename != "" {
		w.Header().Set("Content-Disposition", `filename="`+fileNameEscaper.Replace(filename)+`"`)
	}
	w.Header().Set("Accept-Ranges", "bytes")
	if r.Method == "HEAD" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		return nil
	}
	rangeReq := r.Header.Get("Range")
	if rangeReq == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		if _, e = rs.Seek(0, 0); e != nil {
			return e
		}
		_, e = io.Copy(w, rs)
		return e
	}

	//the rest is dealing with partial content request
	//mostly copy from src/pkg/net/http/fs.go
	ranges, err := parseRange(rangeReq, totalSize)
	if err != nil {
		http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
		return nil
	}
	if sumRangesSize(ranges) > totalSize {
		// The total number of bytes in all the ranges
		// is larger than the size of the file by
		// itself, so this is probably an attack, or a
		// dumb client.  Ignore the range request.
		return nil
	}
	if len(ranges) == 0 {
		return nil
	}
	if len(ranges) == 1 {
		// RFC 2616, Section 14.16:
		// "When an HTTP message includes the content of a single
		// range (for example, a response to a request for a
		// single range, or to a request for a set of ranges
		// that overlap without any holes), this content is
		// transmitted with a Content-Range header, and a
		// Content-Length header showing the number of bytes
		// actually transferred.
		// ...
		// A response to a request for a single range MUST NOT
		// be sent using the multipart/byteranges media type."
		ra := ranges[0]
		w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
		w.Header().Set("Content-Range", ra.contentRange(totalSize))
		w.WriteHeader(http.StatusPartialContent)
		if _, e = rs.Seek(ra.start, 0); e != nil {
			return e
		}

		_, e = io.CopyN(w, rs, ra.length)
		return e
	}
	// process multiple ranges
	for _, ra := range ranges {
		if ra.start > totalSize {
			http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
			return nil
		}
	}
	sendSize := rangesMIMESize(ranges, mimeType, totalSize)
	pr, pw := io.Pipe()
	mw := multipart.NewWriter(pw)
	w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
	sendContent := pr
	defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
	go func() {
		for _, ra := range ranges {
			part, e := mw.CreatePart(ra.mimeHeader(mimeType, totalSize))
			if e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = rs.Seek(ra.start, 0); e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = io.CopyN(part, rs, ra.length); e != nil {
				pw.CloseWithError(e)
				return
			}
		}
		mw.Close()
		pw.Close()
	}()
	if w.Header().Get("Content-Encoding") == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
	}
	w.WriteHeader(http.StatusPartialContent)
	_, e = io.CopyN(w, sendContent, sendSize)
	return e
}
<commit_msg>update<commit_after>package weed_server

import (
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"strconv"
	"strings"
	"time"

	"path"

	"bytes"

	"github.com/chrislusf/seaweedfs/go/glog"
	"github.com/chrislusf/seaweedfs/go/images"
	"github.com/chrislusf/seaweedfs/go/operation"
	"github.com/chrislusf/seaweedfs/go/storage"
	"github.com/chrislusf/seaweedfs/go/util"
)

var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
	n := new(storage.Needle)
	vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
	volumeId, err := storage.NewVolumeId(vid)
	if err != nil {
		glog.V(2).Infoln("parsing error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	err = n.ParsePath(fid)
	if err != nil {
		glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	glog.V(4).Infoln("volume", volumeId, "reading", n)
	if !vs.store.HasVolume(volumeId) {
		if !vs.ReadRedirect {
			glog.V(2).Infoln("volume is not local:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
			return
		}
		lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
		glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
		if err == nil && len(lookupResult.Locations) > 0 {
			http.Redirect(w, r, util.NormalizeUrl(lookupResult.Locations[0].PublicUrl)+r.URL.Path, http.StatusMovedPermanently)
		} else {
			glog.V(2).Infoln("lookup error:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
		}
		return
	}
	cookie := n.Cookie
	count, e := vs.store.ReadVolumeNeedle(volumeId, n)
	glog.V(4).Infoln("read bytes", count, "error", e)
	if e != nil || count <= 0 {
		glog.V(0).Infoln("read error:", e, r.URL.Path)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if n.Cookie != cookie {
		glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if n.LastModified != 0 {
		w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
		if r.Header.Get("If-Modified-Since") != "" {
			if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
				if t.Unix() >= int64(n.LastModified) {
					w.WriteHeader(http.StatusNotModified)
					return
				}
			}
		}
	}
	etag := n.Etag()
	if inm := r.Header.Get("If-None-Match"); inm == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}
	w.Header().Set("Etag", etag)

	if vs.tryHandleChunkedFile(n, filename, w, r) {
		return
	}

	if n.NameSize > 0 && filename == "" {
		filename = string(n.Name)
		if ext == "" {
			ext = path.Ext(filename)
		}
	}
	mtype := ""
	if n.MimeSize > 0 {
		mt := string(n.Mime)
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mtype = mt
		}
	}

	if ext != ".gz" {
		if n.IsGzipped() {
			if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
				w.Header().Set("Content-Encoding", "gzip")
			} else {
				if n.Data, err = operation.UnGzipData(n.Data); err != nil {
					glog.V(0).Infoln("ungzip error:", err, r.URL.Path)
				}
			}
		}
	}
	if ext == ".png" || ext == ".jpg" || ext == ".gif" {
		width, height := 0, 0
		if r.FormValue("width") != "" {
			width, _ = strconv.Atoi(r.FormValue("width"))
		}
		if r.FormValue("height") != "" {
			height, _ = strconv.Atoi(r.FormValue("height"))
		}
		n.Data, _, _ = images.Resized(ext, n.Data, width, height)
	}

	if e := writeResponseContent(filename, mtype, bytes.NewReader(n.Data), w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
}

func (vs *VolumeServer) tryHandleChunkedFile(n *storage.Needle, fileName string, w http.ResponseWriter, r *http.Request) (processed bool) {
	if !n.IsChunkedManifest() {
		return false
	}
	raw, _ := strconv.ParseBool(r.FormValue("raw"))
	if raw {
		return false
	}
	processed = true

	chunkManifest, e := operation.LoadChunkManifest(n.Data, n.IsGzipped())
	if e != nil {
		glog.V(0).Infof("load chunked manifest (%s) error: %s", r.URL.Path, e.Error())
		return false
	}
	if fileName == "" && chunkManifest.Name != "" {
		fileName = chunkManifest.Name
	}
	mType := ""
	if chunkManifest.Mime != "" {
		mt := chunkManifest.Mime
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mType = mt
		}
	}

	w.Header().Set("X-File-Store", "chunked")

	chunkedFileReader := &operation.ChunkedFileReader{
		Manifest: chunkManifest,
		Master:   vs.GetMasterNode(),
	}
	defer chunkedFileReader.Close()
	if e := writeResponseContent(fileName, mType, chunkedFileReader, w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
	return
}

func writeResponseContent(filename, mimeType string, rs io.ReadSeeker, w http.ResponseWriter, r *http.Request) error {
	totalSize, e := rs.Seek(0, 2)
	if mimeType == "" {
		if ext := path.Ext(filename); ext != "" {
			mimeType = mime.TypeByExtension(ext)
		}
	}
	if mimeType != "" {
		w.Header().Set("Content-Type", mimeType)
	}
	if filename != "" {
		w.Header().Set("Content-Disposition", `filename="`+fileNameEscaper.Replace(filename)+`"`)
	}
	w.Header().Set("Accept-Ranges", "bytes")
	if r.Method == "HEAD" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		return nil
	}
	rangeReq := r.Header.Get("Range")
	if rangeReq == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		if _, e = rs.Seek(0, 0); e != nil {
			return e
		}
		_, e = io.Copy(w, rs)
		return e
	}

	//the rest is dealing with partial content request
	//mostly copy from src/pkg/net/http/fs.go
	ranges, err := parseRange(rangeReq, totalSize)
	if err != nil {
		http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
		return nil
	}
	if sumRangesSize(ranges) > totalSize {
		// The total number of bytes in all the ranges
		// is larger than the size of the file by
		// itself, so this is probably an attack, or a
		// dumb client.  Ignore the range request.
		return nil
	}
	if len(ranges) == 0 {
		return nil
	}
	if len(ranges) == 1 {
		// RFC 2616, Section 14.16:
		// "When an HTTP message includes the content of a single
		// range (for example, a response to a request for a
		// single range, or to a request for a set of ranges
		// that overlap without any holes), this content is
		// transmitted with a Content-Range header, and a
		// Content-Length header showing the number of bytes
		// actually transferred.
		// ...
		// A response to a request for a single range MUST NOT
		// be sent using the multipart/byteranges media type."
		ra := ranges[0]
		w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
		w.Header().Set("Content-Range", ra.contentRange(totalSize))
		w.WriteHeader(http.StatusPartialContent)
		if _, e = rs.Seek(ra.start, 0); e != nil {
			return e
		}

		_, e = io.CopyN(w, rs, ra.length)
		return e
	}
	// process multiple ranges
	for _, ra := range ranges {
		if ra.start > totalSize {
			http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
			return nil
		}
	}
	sendSize := rangesMIMESize(ranges, mimeType, totalSize)
	pr, pw := io.Pipe()
	mw := multipart.NewWriter(pw)
	w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
	sendContent := pr
	defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
	go func() {
		for _, ra := range ranges {
			part, e := mw.CreatePart(ra.mimeHeader(mimeType, totalSize))
			if e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = rs.Seek(ra.start, 0); e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = io.CopyN(part, rs, ra.length); e != nil {
				pw.CloseWithError(e)
				return
			}
		}
		mw.Close()
		pw.Close()
	}()
	if w.Header().Get("Content-Encoding") == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
	}
	w.WriteHeader(http.StatusPartialContent)
	_, e = io.CopyN(w, sendContent, sendSize)
	return e
}
<|endoftext|>


<commit_before>// Brief: Scripture API
// Primary responsibility: Query calls for Scripture functionality

package main

import (
	"fmt"
	"log"
	"net/url"

	bmul "github.com/julwrites/BotMultiplexer"
)

var passageQuery string = "https://www.biblegateway.com/passage/?search=%s&version=%s"

func GetReference(ref string, env *bmul.SessionData) string {
	query := fmt.Sprintf(passageQuery, ref, GetUserConfig(&env.User).Version)

	doc := GetHtml(query)

	if doc == nil {
		log.Fatalf("Error getting reference")
		return ""
	}

	foundRef, err := Find(doc, "passage-display-bcv")
	if err != nil {
		log.Fatalf("Error in Finding of Reference: %v", err)
	}

	return foundRef.Data
}

func GetPassage(ref string, env *bmul.SessionData) string {
	query := fmt.Sprintf(passageQuery, ref, GetUserConfig(&env.User).Version)
	query = url.QueryEscape(query)

	doc := GetHtml(query)

	if doc == nil {
		log.Fatalf("Error getting reference")
		return ""
	}

	foundRef, err := Find(doc, "passage-display-bcv")
	if err != nil {
		log.Fatalf("Error in Finding of Reference: %v", err)
	}

	return foundRef.Data
}

func GetBiblePassage(env *bmul.SessionData) bool {
	if len(env.Msg.Message) > 0 {

		log.Printf("Get Bible Passage %s", env.Msg.Message)
		ref := GetReference(env.Msg.Message, env)

		if len(ref) > 0 {
			env.Res.Message = GetPassage(ref, env)

			return true
		}
	}

	return false
}
<commit_msg>Logging query<commit_after>// Brief: Scripture API
// Primary responsibility: Query calls for Scripture functionality

package main

import (
	"fmt"
	"log"
	"net/url"

	bmul "github.com/julwrites/BotMultiplexer"
)

var passageQuery string = "https://www.biblegateway.com/passage/?search=%s&version=%s"

func GetReference(ref string, env *bmul.SessionData) string {
	query := fmt.Sprintf(passageQuery, ref, GetUserConfig(&env.User).Version)

	log.Printf("Do Query %s", query)

	doc := GetHtml(query)

	if doc == nil {
		log.Fatalf("Error getting reference")
		return ""
	}

	foundRef, err := Find(doc, "passage-display-bcv")
	if err != nil {
		log.Fatalf("Error in Finding of Reference: %v", err)
	}

	return foundRef.Data
}

func GetPassage(ref string, env *bmul.SessionData) string {
	query := fmt.Sprintf(passageQuery, ref, GetUserConfig(&env.User).Version)
	query = url.QueryEscape(query)

	doc := GetHtml(query)

	if doc == nil {
		log.Fatalf("Error getting reference")
		return ""
	}

	foundRef, err := Find(doc, "passage-display-bcv")
	if err != nil {
		log.Fatalf("Error in Finding of Reference: %v", err)
	}

	return foundRef.Data
}

func GetBiblePassage(env *bmul.SessionData) bool {
	if len(env.Msg.Message) > 0 {

		ref := GetReference(env.Msg.Message, env)

		if len(ref) > 0 {
			env.Res.Message = GetPassage(ref, env)

			return true
		}
	}

	return false
}
<|endoftext|>


<commit_before>package resque

import (
	"encoding/json"
	"sync"

	"github.com/go-redis/redis"
)

// Performer interface that each consumer/worker must implement.
type Performer interface {
	// Perform consumes the given payload.
	Perform(args ...JobArgument)
}

// Worker primitive to implement a consumer.
type Worker struct {
	JobClass JobClass
	Done     <-chan bool
}

// Run .
func (w Worker) Run() error {
	var wg sync.WaitGroup

	pubsub, err := w.JobClass.Queue.Subscribe()
	if err != nil {
		return err
	}
	input := make(chan Job, 1)

	defer func() {
		wg.Wait()
		pubsub.Close()
	}()

	go consume(pubsub, input)

	for {
		select {
		case job := <-input:
			w.JobClass.Performer.Perform(job.Args...)
		case <-w.Done:
			close(input)
			return nil
		}
	}
}

func consume(pubsub *redis.PubSub, c <-chan Job) {
	var job Job

	defer func() { recover() }()

	for {
		msg, err := pubsub.ReceiveMessage()
		if err != nil {
			continue
		}

		job = Job{}
		err = json.Unmarshal([]byte(msg.Payload), &job)
		if err != nil {
			continue
		}

		c <- job
	}

	return
}
<commit_msg>worker.go: - added license header - added consume() - channels: input, error and quit.<commit_after>package resque

import (
	"encoding/json"
	"sync"

	"github.com/go-redis/redis"
)

// Performer interface that each consumer/worker must implement.
type Performer interface {
	// Perform consumes the given payload.
	Perform(args ...JobArgument)
}

// Worker primitive to implement a consumer.
type Worker struct {
	// JobClass type of job being of executed.
	JobClass JobClass
	// Done channel to request the worker to stop execution.
	Done <-chan bool
}

// Run worker until an error occurs or the worker is requested to quit.
func (w Worker) Run() error {
	pubsub, err := w.JobClass.Queue.Subscribe()
	if err != nil {
		return err
	}

	chan_in := make(chan Job, 1)
	chan_err := make(chan error, 1)
	chan_exit := make(chan bool, 1)

	var wg sync.WaitGroup

	defer func() {
		wg.Wait()
		close(chan_in)
		close(chan_err)
		close(chan_exit)
		pubsub.Close()
	}()

	go consume(pubsub, chan_in, chan_err, chan_exit, wg)
	chan_exit <- false

	for {
		select {
		case job := <-chan_in:
			w.JobClass.Performer.Perform(job.Args...)

			// ready for the next message
			chan_exit <- false

		case err = chan_err:
			chan_exit <- true
			return err

		case <-w.Done:
			chan_exit <- true
			return nil
		}
	}
}

// consume retrieves a message from a Redis subscription,
// transforms it into a Job and
// send to the input channel.
// In case of error during this process,
// it will send the error message to an error channel.
func consume(pubsub *redis.PubSub, chan_in <-chan Job, chan_err <-chan error, chan_exit <-chan bool, wg sync.WaitGroup) {
	defer wg.Done()

	var job Job

	for {
		select {
		case quit := <-chan_exit:
			if quit {
				return
			}

			for {
				msg, err := pubsub.ReceiveMessage()
				if err != nil {
					chan_err <- err
					continue
				}

				job = Job{}
				err = json.Unmarshal([]byte(msg.Payload), &job)
				if err != nil {
					chan_err <- err
					continue
				}

				chan_in <- job
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "math/rand"
import "os"

var (
	SIGMA  = [62]int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
	SIGMA2 = [91]int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '"', '$', '%', '&', '\\', '(', ')', '*', '+', ',', '-', '/', ':', ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~'}
	DX     = [4]int{0, 1, 0, -1}
	DY     = [4]int{-1, 0, 1, 0}
)

func square(x int) int {
	return x * x
}

// Generate and prints a width x height puzzle of numberlink
// The algorithm works as follows:
// 1) First the board is tiled with 2x1 dominos in a simple, deterministic way.
//    If this is not possible (on an odd area paper), the bottom right corner is
// 	  left as a singleton
// 2) Then the dominos are randomly shuffled by rotating random pairs of neighbours.
//    This is is not done in the case of width or height equal to 1
// 3) Now, in the case of an odd area paper, the bottom right corner is attached to
//    one of its neighbour dominos. This will always be possible.
// 4) Finally we can start finding random paths through the dominos, combining them
//    as we pass through. Special care is taken not to connect 'neighboour flows'
//    which would create puzzles that 'double back on themselves'
// 5) Before the puzzle is printed we 'compact' the range of colors used, as much as possible
// 6) The puzzle is printed by replacing all positions that aren't flow-heads with a .
func Generate(width, height int) error {
	if width == 0 || height == 0 || width == 1 && height == 1 {
		return fmt.Errorf("Error: Requires bigger paper size")
	}
	table := tile(width, height)
	shuffle(table)
	oddCorner(table)
	findFlows(table)
	return print(table)
}

func print(table [][]int) error {
	width, height := len(table[0]), len(table)
	colorsUsed := flatten(table)
	if colorsUsed > len(SIGMA2) {
		return fmt.Errorf("Error: Not enough printable characters to print puzzle")
	} else if colorsUsed > len(SIGMA) {
		fmt.Fprintf(os.Stderr, "Warning: Including non-standard characters in puzzle\n\n")
	}

	fmt.Println(width, height)
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if isFlowHead(x, y, table) {
				fmt.Printf("%c", SIGMA2[table[y][x]])
			} else {
				fmt.Printf(".")
			}
		}
		fmt.Println()
	}
	fmt.Println()

	return nil
}

func tile(width, height int) [][]int {
	table := make([][]int, height)
	for y := 0; y < height; y++ {
		table[y] = make([]int, width)
	}
	// Start with simple vertical tiling
	alpha := int(0)
	for y := 0; y < height-1; y += 2 {
		for x := 0; x < width; x++ {
			table[y][x] = alpha
			table[y+1][x] = alpha
			alpha += 1
		}
	}
	// Add padding in case of odd height
	if height%2 == 1 {
		for x := 0; x < width-1; x += 2 {
			table[height-1][x] = alpha
			table[height-1][x+1] = alpha
			alpha += 1
		}
		// In case of odd width, add a single in the corner.
		// We will merge it into a real flow after shuffeling
		if width%2 == 1 {
			table[height-1][width-1] = alpha
		}
	}
	return table
}

func shuffle(table [][]int) {
	width, height := len(table[0]), len(table)
	if width == 1 || height == 1 {
		return
	}
	for i := 0; i < square(width*height); i++ {
		x, y := rand.Intn(width-1), rand.Intn(height-1)
		if table[y][x] == table[y][x+1] && table[y+1][x] == table[y+1][x+1] {
			// Horizontal case
			// aa \ ab
			// bb / ab
			table[y+1][x] = table[y][x]
			table[y][x+1] = table[y+1][x+1]
		} else if table[y][x] == table[y+1][x] && table[y][x+1] == table[y+1][x+1] {
			// Vertical case
			// ab \ aa
			// ab / bb
			table[y][x+1] = table[y][x]
			table[y+1][x] = table[y+1][x+1]
		}
	}
}

func oddCorner(table [][]int) {
	width, height := len(table[0]), len(table)
	if width%2 == 1 && height%2 == 1 {
		// Horizontal case:
		// aax
		if width > 2 && table[height-1][width-3] == table[height-1][width-2] {
			table[height-1][width-1] = table[height-1][width-2]
		}
		// Vertical case:
		// ab
		// ax
		if height > 2 && table[height-3][width-1] == table[height-2][width-1] {
			table[height-1][width-1] = table[height-2][width-1]
		}
	}
}

func canConnect(x1, y1, x2, y2 int, table [][]int) bool {
	width, height := len(table[0]), len(table)
	// Check (x1,y2) and (x2,y2) are flow heads
	if table[y1][x1] == table[y2][x2] {
		return false
	}
	if !isFlowHead(x1, y1, table) || !isFlowHead(x2, y2, table) {
		return false
	}
	for y3 := 0; y3 < height; y3++ {
		for x3 := 0; x3 < width; x3++ {
			for i := 0; i < 4; i++ {
				x4, y4 := x3+DX[i], y3+DY[i]
				if inside(x4, y4, width, height) &&
					!(x3 == x1 && y3 == y1 && x4 == x2 && y4 == y2) &&
					table[y3][x3] == table[y1][x1] && table[y4][x4] == table[y2][x2] {
					return false
				}
			}
		}
	}
	return true
}

func isFlowHead(x, y int, table [][]int) bool {
	width, height := len(table[0]), len(table)
	degree := 0
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && table[y1][x1] == table[y][x] {
			degree += 1
		}
	}
	return degree < 2
}

func fill(x, y int, alpha int, table [][]int) {
	width, height := len(table[0]), len(table)
	orig := table[y][x]
	table[y][x] = alpha
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && table[y1][x1] == orig {
			fill(x1, y1, alpha, table)
		}
	}
}

func findFlows(table [][]int) {
	width, height := len(table[0]), len(table)
	for i := 0; i < square(width*height); i++ {
		x, y := rand.Intn(width), rand.Intn(height)
		if isFlowHead(x, y, table) {
			layFlow(x, y, table)
		}
	}
}

func layFlow(x, y int, table [][]int) {
	width, height := len(table[0]), len(table)
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && canConnect(x, y, x1, y1, table) {
			fill(x1, y1, table[y][x], table)
			x2, y2 := follow(x1, y1, x, y, table)
			layFlow(x2, y2, table)
			return
		}
	}
}

func follow(x, y, x0, y0 int, table [][]int) (int, int) {
	width, height := len(table[0]), len(table)
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && !(x1 == x0 && y1 == y0) &&
			table[y][x] == table[y1][x1] {
			return follow(x1, y1, x, y, table)
		}
	}
	return x, y
}

func inside(x, y int, width, height int) bool {
	return 0 <= x && x < width && 0 <= y && y < height
}

func flatten(table [][]int) int {
	width, height := len(table[0]), len(table)
	MAX := 100
	alpha := MAX
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if table[y][x] < MAX {
				fill(x, y, alpha, table)
				alpha += 1
			}
		}
	}
	alpha = 0
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if table[y][x] >= MAX {
				fill(x, y, alpha, table)
				alpha += 1
			}
		}
	}
	return alpha
}
<commit_msg>Seeded the generator and made it a little more random<commit_after>package main

import "fmt"
import "math/rand"
import "os"
import "time"

var (
	SIGMA  = [62]int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
	SIGMA2 = [91]int{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '"', '$', '%', '&', '\\', '(', ')', '*', '+', ',', '-', '/', ':', ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~'}
	DX     = [4]int{0, 1, 0, -1}
	DY     = [4]int{-1, 0, 1, 0}
)

func square(x int) int {
	return x * x
}

// Generate and prints a width x height puzzle of numberlink
// The algorithm works as follows:
// 1) First the board is tiled with 2x1 dominos in a simple, deterministic way.
//    If this is not possible (on an odd area paper), the bottom right corner is
// 	  left as a singleton
// 2) Then the dominos are randomly shuffled by rotating random pairs of neighbours.
//    This is is not done in the case of width or height equal to 1
// 3) Now, in the case of an odd area paper, the bottom right corner is attached to
//    one of its neighbour dominos. This will always be possible.
// 4) Finally we can start finding random paths through the dominos, combining them
//    as we pass through. Special care is taken not to connect 'neighboour flows'
//    which would create puzzles that 'double back on themselves'
// 5) Before the puzzle is printed we 'compact' the range of colors used, as much as possible
// 6) The puzzle is printed by replacing all positions that aren't flow-heads with a .
func Generate(width, height int) error {
	if width == 0 || height == 0 || width == 1 && height == 1 {
		return fmt.Errorf("Error: Requires bigger paper size")
	}
	rand.Seed(time.Now().UTC().UnixNano())
	table := tile(width, height)
	shuffle(table)
	oddCorner(table)
	findFlows(table)
	return print(table)
}

func print(table [][]int) error {
	width, height := len(table[0]), len(table)
	colorsUsed := flatten(table)
	if colorsUsed > len(SIGMA2) {
		return fmt.Errorf("Error: Not enough printable characters to print puzzle")
	} else if colorsUsed > len(SIGMA) {
		fmt.Fprintf(os.Stderr, "Warning: Including non-standard characters in puzzle\n\n")
	}

	fmt.Println(width, height)
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if isFlowHead(x, y, table) {
				fmt.Printf("%c", SIGMA2[table[y][x]])
			} else {
				fmt.Printf(".")
			}
		}
		fmt.Println()
	}
	fmt.Println()

	return nil
}

func tile(width, height int) [][]int {
	table := make([][]int, height)
	for y := 0; y < height; y++ {
		table[y] = make([]int, width)
	}
	// Start with simple vertical tiling
	alpha := int(0)
	for y := 0; y < height-1; y += 2 {
		for x := 0; x < width; x++ {
			table[y][x] = alpha
			table[y+1][x] = alpha
			alpha += 1
		}
	}
	// Add padding in case of odd height
	if height%2 == 1 {
		for x := 0; x < width-1; x += 2 {
			table[height-1][x] = alpha
			table[height-1][x+1] = alpha
			alpha += 1
		}
		// In case of odd width, add a single in the corner.
		// We will merge it into a real flow after shuffeling
		if width%2 == 1 {
			table[height-1][width-1] = alpha
		}
	}
	return table
}

func shuffle(table [][]int) {
	width, height := len(table[0]), len(table)
	if width == 1 || height == 1 {
		return
	}
	for i := 0; i < square(width*height); i++ {
		x, y := rand.Intn(width-1), rand.Intn(height-1)
		if table[y][x] == table[y][x+1] && table[y+1][x] == table[y+1][x+1] {
			// Horizontal case
			// aa \ ab
			// bb / ab
			table[y+1][x] = table[y][x]
			table[y][x+1] = table[y+1][x+1]
		} else if table[y][x] == table[y+1][x] && table[y][x+1] == table[y+1][x+1] {
			// Vertical case
			// ab \ aa
			// ab / bb
			table[y][x+1] = table[y][x]
			table[y+1][x] = table[y+1][x+1]
		}
	}
}

func oddCorner(table [][]int) {
	width, height := len(table[0]), len(table)
	if width%2 == 1 && height%2 == 1 {
		// Horizontal case:
		// aax
		if width > 2 && table[height-1][width-3] == table[height-1][width-2] {
			table[height-1][width-1] = table[height-1][width-2]
		}
		// Vertical case:
		// ab
		// ax
		if height > 2 && table[height-3][width-1] == table[height-2][width-1] {
			table[height-1][width-1] = table[height-2][width-1]
		}
	}
}

func canConnect(x1, y1, x2, y2 int, table [][]int) bool {
	width, height := len(table[0]), len(table)
	// Check (x1,y2) and (x2,y2) are flow heads
	if table[y1][x1] == table[y2][x2] {
		return false
	}
	if !isFlowHead(x1, y1, table) || !isFlowHead(x2, y2, table) {
		return false
	}
	for y3 := 0; y3 < height; y3++ {
		for x3 := 0; x3 < width; x3++ {
			for i := 0; i < 4; i++ {
				x4, y4 := x3+DX[i], y3+DY[i]
				if inside(x4, y4, width, height) &&
					!(x3 == x1 && y3 == y1 && x4 == x2 && y4 == y2) &&
					table[y3][x3] == table[y1][x1] && table[y4][x4] == table[y2][x2] {
					return false
				}
			}
		}
	}
	return true
}

func isFlowHead(x, y int, table [][]int) bool {
	width, height := len(table[0]), len(table)
	degree := 0
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && table[y1][x1] == table[y][x] {
			degree += 1
		}
	}
	return degree < 2
}

func fill(x, y int, alpha int, table [][]int) {
	width, height := len(table[0]), len(table)
	orig := table[y][x]
	table[y][x] = alpha
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && table[y1][x1] == orig {
			fill(x1, y1, alpha, table)
		}
	}
}

func findFlows(table [][]int) {
	width, height := len(table[0]), len(table)
	for i := 0; i < square(width*height); i++ {
		x, y := rand.Intn(width), rand.Intn(height)
		if isFlowHead(x, y, table) {
			layFlow(x, y, table)
		}
	}
}

func layFlow(x, y int, table [][]int) {
	width, height := len(table[0]), len(table)
	for i := 0; i < 4; i++ {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && canConnect(x, y, x1, y1, table) {
			fill(x1, y1, table[y][x], table)
			x2, y2 := follow(x1, y1, x, y, table)
			layFlow(x2, y2, table)
			return
		}
	}
}

func follow(x, y, x0, y0 int, table [][]int) (int, int) {
	width, height := len(table[0]), len(table)
	for _, i := range rand.Perm(4) {
		x1, y1 := x+DX[i], y+DY[i]
		if inside(x1, y1, width, height) && !(x1 == x0 && y1 == y0) &&
			table[y][x] == table[y1][x1] {
			return follow(x1, y1, x, y, table)
		}
	}
	return x, y
}

func inside(x, y int, width, height int) bool {
	return 0 <= x && x < width && 0 <= y && y < height
}

func flatten(table [][]int) int {
	width, height := len(table[0]), len(table)
	MAX := 100
	alpha := MAX
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if table[y][x] < MAX {
				fill(x, y, alpha, table)
				alpha += 1
			}
		}
	}
	alpha = 0
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if table[y][x] >= MAX {
				fill(x, y, alpha, table)
				alpha += 1
			}
		}
	}
	return alpha
}
<|endoftext|>


<commit_before>package sms

import (
	"errors"

	nexmo "github.com/njern/gonexmo"
	"github.com/skygeario/skygear-server/pkg/core/config"
)

type NexmoClient struct {
	From string
	*nexmo.Client
}

func NewNexmoClient(c config.NexmoConfiguration) *NexmoClient {
	if c.APIKey == "" || c.APISecret == "" {
		panic(errors.New("Nexmo api key or secret is empty"))
	}

	client, _ := nexmo.NewClient(c.APIKey, c.APISecret)
	return &NexmoClient{
		From:   c.From,
		Client: client,
	}
}

func (n *NexmoClient) Send(to string, body string) error {
	message := nexmo.SMSMessage{
		From:  n.From,
		To:    to,
		Type:  nexmo.Text,
		Text:  body,
		Class: nexmo.Standard,
	}

	resp, err := n.SMS.Send(&message)
	if err != nil {
		return err
	}

	if resp.MessageCount == 0 {
		err = errors.New("Unable to send sms")
		return err
	}

	return nil
}
<commit_msg>Return nexmo client error if exists<commit_after>package sms

import (
	"errors"

	nexmo "github.com/njern/gonexmo"
	"github.com/skygeario/skygear-server/pkg/core/config"
)

type NexmoClient struct {
	From string
	*nexmo.Client
}

func NewNexmoClient(c config.NexmoConfiguration) *NexmoClient {
	if c.APIKey == "" || c.APISecret == "" {
		panic(errors.New("Nexmo api key or secret is empty"))
	}

	client, _ := nexmo.NewClient(c.APIKey, c.APISecret)
	return &NexmoClient{
		From:   c.From,
		Client: client,
	}
}

func (n *NexmoClient) Send(to string, body string) error {
	message := nexmo.SMSMessage{
		From:  n.From,
		To:    to,
		Type:  nexmo.Text,
		Text:  body,
		Class: nexmo.Standard,
	}

	resp, err := n.SMS.Send(&message)
	if err != nil {
		return err
	}

	if resp.MessageCount == 0 {
		err = errors.New("No sms is sent")
		return err
	}

	report := resp.Messages[0]
	if report.ErrorText != "" {
		err = errors.New(report.ErrorText)
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package invdendpoint

const PlanEndpoint = "/plans"

type Plan struct {
	Id            string                 `json:"id,omitempty"`
	Object        string                 `json:"object,omitempty"`
	Item          string                 `json:"catalog_item,omitempty"`
	Name          string                 `json:"name,omitempty"`
	Currency      string                 `json:"currency,omitempty"`
	Amount        float64                `json:"amount,omitempty"`
	PricingMode   string                 `json:"pricing_mode,omitempty"`
	QuantityType  string                 `json:"quantity_type,omitempty"`
	Interval      string                 `json:"interval,omitempty"`
	IntervalCount float64                `json:"interval_count,omitempty"`
	Tiers         []Tier                 `json:"tier,omitempty"`
	CreatedAt     int64                  `json:"created_at,omitempty"`
	Metadata      map[string]interface{} `json:"updated_at,omitempty"`
}

type Tier struct {
	MaxQty   float64 `json:"max_qty,omitempty"`
	UnitCost float64 `json:"unit_cost,omitempty"`
	MinQty   float64 `json:"min_qty,omitempty"`
}
<commit_msg>add numberofsubscriptions to plans object<commit_after>package invdendpoint

const PlanEndpoint = "/plans"

type Plan struct {
	Id            string                 `json:"id,omitempty"`
	Object        string                 `json:"object,omitempty"`
	Item          string                 `json:"catalog_item,omitempty"`
	Name          string                 `json:"name,omitempty"`
	Currency      string                 `json:"currency,omitempty"`
	Amount        float64                `json:"amount,omitempty"`
	PricingMode   string                 `json:"pricing_mode,omitempty"`
	QuantityType  string                 `json:"quantity_type,omitempty"`
	Interval      string                 `json:"interval,omitempty"`
	IntervalCount float64                `json:"interval_count,omitempty"`
	Tiers         []Tier                 `json:"tier,omitempty"`
	CreatedAt     int64                  `json:"created_at,omitempty"`
	Metadata      map[string]interface{} `json:"updated_at,omitempty"`
	NumberOfSubscriptions *int           `json:"num_subscriptions,omitempty"`
}

type Tier struct {
	MaxQty   float64 `json:"max_qty,omitempty"`
	UnitCost float64 `json:"unit_cost,omitempty"`
	MinQty   float64 `json:"min_qty,omitempty"`
}
<|endoftext|>


<commit_before>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * rpc/service.go                                         *
 *                                                        *
 * hprose service for Go.                                 *
 *                                                        *
 * LastModified: Sep 12, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

package rpc

import (
	"crypto/rand"
	"fmt"
	"reflect"
	"time"
	"unsafe"

	"github.com/hprose/hprose-golang/io"
	"github.com/hprose/hprose-golang/promise"
)

// Service interface
type Service interface {
	AddFunction(name string, function interface{}, options MethodOptions) Service
	AddFunctions(names []string, functions []interface{}, options MethodOptions) Service
	AddMethod(name string, obj interface{}, options MethodOptions, alias ...string) Service
	AddMethods(names []string, obj interface{}, options MethodOptions, aliases ...[]string) Service
	AddInstanceMethods(obj interface{}, options MethodOptions) Service
	AddAllMethods(obj interface{}, options MethodOptions) Service
	AddMissingMethod(method MissingMethod, options MethodOptions) Service
	Remove(name string) Service
	Filter() Filter
	FilterByIndex(index int) Filter
	SetFilter(filter ...Filter) Service
	AddFilter(filter ...Filter) Service
	RemoveFilterByIndex(index int) Service
	RemoveFilter(filter ...Filter) Service
	AddInvokeHandler(handler InvokeHandler) Service
	AddBeforeFilterHandler(handler FilterHandler) Service
	AddAfterFilterHandler(handler FilterHandler) Service
}

type fixer interface {
	FixArguments(args []reflect.Value, lastParamType reflect.Type, context Context) []reflect.Value
}

func fixArguments(args []reflect.Value, lastParamType reflect.Type, context Context) []reflect.Value {
	typ := (*emptyInterface)(unsafe.Pointer(&lastParamType)).ptr
	if typ == interfaceType || typ == contextType {
		args = append(args, reflect.ValueOf(context))
	}
	return args
}

// BaseService is the hprose base service
type BaseService struct {
	*methodManager
	*handlerManager
	*filterManager
	fixer
	Event      ServiceEvent
	Debug      bool
	Simple     bool
	Timeout    time.Duration
	Heartbeat  time.Duration
	ErrorDelay time.Duration
	allTopics  map[string]map[string]*topic
}

// GetNextID is the default method for client uid
func GetNextID() (uid string) {
	u := make([]byte, 16)
	rand.Read(u)
	u[6] = (u[6] & 0x0f) | 0x40
	u[8] = (u[8] & 0x3f) | 0x80
	uid = fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
	return
}

// NewBaseService is the constructor for BaseService
func NewBaseService() (service *BaseService) {
	service = new(BaseService)
	service.methodManager = newMethodManager()
	service.handlerManager = newHandlerManager()
	service.filterManager = &filterManager{}
	service.Timeout = 120 * 1000 * 1000
	service.Heartbeat = 3 * 1000 * 1000
	service.ErrorDelay = 10 * 1000 * 1000
	service.allTopics = make(map[string]map[string]*topic)
	service.AddFunction("#", GetNextID, MethodOptions{Simple: true})
	return service
}

// AddFunction publish a func or bound method
// name is the method name
// function is a func or bound method
// options includes Mode, Simple, Oneway and NameSpace
func (service *BaseService) AddFunction(name string, function interface{}, options MethodOptions) Service {
	service.methodManager.AddFunction(name, function, options)
	return service
}

// AddFunctions is used for batch publishing service method
func (service *BaseService) AddFunctions(names []string, functions []interface{}, options MethodOptions) Service {
	service.methodManager.AddFunctions(names, functions, options)
	return service
}

// AddMethod is used for publishing a method on the obj with an alias
func (service *BaseService) AddMethod(name string, obj interface{}, options MethodOptions, alias ...string) Service {
	service.methodManager.AddMethod(name, obj, options, alias...)
	return service
}

// AddMethods is used for batch publishing methods on the obj with aliases
func (service *BaseService) AddMethods(names []string, obj interface{}, options MethodOptions, aliases ...[]string) Service {
	service.methodManager.AddMethods(names, obj, options, aliases...)
	return service
}

// AddInstanceMethods is used for publishing all the public methods and func fields with options.
func (service *BaseService) AddInstanceMethods(obj interface{}, options MethodOptions) Service {
	service.methodManager.AddInstanceMethods(obj, options)
	return service
}

// AddAllMethods will publish all methods and non-nil function fields on the
// obj self and on its anonymous or non-anonymous struct fields (or pointer to
// pointer ... to pointer struct fields). This is a recursive operation.
// So it's a pit, if you do not know what you are doing, do not step on.
func (service *BaseService) AddAllMethods(obj interface{}, options MethodOptions) Service {
	service.methodManager.AddAllMethods(obj, options)
	return service
}

// AddMissingMethod is used for publishing a method,
// all methods not explicitly published will be redirected to this method.
func (service *BaseService) AddMissingMethod(method MissingMethod, options MethodOptions) Service {
	service.methodManager.AddMissingMethod(method, options)
	return service
}

// Remove the published func or method by name
func (service *BaseService) Remove(name string) Service {
	service.methodManager.Remove(name)
	return service
}

// Filter return the first filter
func (service *BaseService) Filter() Filter {
	return service.filterManager.Filter()
}

// FilterByIndex return the filter by index
func (service *BaseService) FilterByIndex(index int) Filter {
	return service.filterManager.FilterByIndex(index)
}

// SetFilter will replace the current filter settings
func (service *BaseService) SetFilter(filter ...Filter) Service {
	service.filterManager.SetFilter(filter...)
	return service
}

// AddFilter add the filter to this Service
func (service *BaseService) AddFilter(filter ...Filter) Service {
	service.filterManager.AddFilter(filter...)
	return service
}

// RemoveFilterByIndex remove the filter by the index
func (service *BaseService) RemoveFilterByIndex(index int) Service {
	service.filterManager.RemoveFilterByIndex(index)
	return service
}

// RemoveFilter remove the filter from this Service
func (service *BaseService) RemoveFilter(filter ...Filter) Service {
	service.filterManager.RemoveFilter(filter...)
	return service
}

// AddInvokeHandler add the invoke handler to this Service
func (service *BaseService) AddInvokeHandler(handler InvokeHandler) Service {
	service.handlerManager.AddInvokeHandler(handler)
	return service
}

// AddBeforeFilterHandler add the filter handler before filters
func (service *BaseService) AddBeforeFilterHandler(handler FilterHandler) Service {
	service.handlerManager.AddBeforeFilterHandler(handler)
	return service
}

// AddAfterFilterHandler add the filter handler after filters
func (service *BaseService) AddAfterFilterHandler(handler FilterHandler) Service {
	service.handlerManager.AddAfterFilterHandler(handler)
	return service
}

func (service *BaseService) callService(
	name string, args []reflect.Value, context ServiceContext) []reflect.Value {
	remoteMethod := context.Method
	function := remoteMethod.Function
	if context.IsMissingMethod {
		missingMethod := function.Interface().(MissingMethod)
		return missingMethod(name, args, context)
	}
	ft := function.Type()
	n := len(args)
	if ft.IsVariadic() {
		return function.CallSlice(args)
	}
	if ft.NumIn() == n+1 {
		args = service.FixArguments(args, ft.In(n), context)
	}
	return function.Call(args)
}

func (service *BaseService) getErrorMessage(err error) string {
	if panicError, ok := err.(*promise.PanicError); ok {
		if service.Debug {
			return fmt.Sprintf("%v\r\n%s", panicError.Panic, panicError.Stack)
		}
		return panicError.Error()
	}
	return err.Error()
}

func (service *BaseService) fireErrorEvent(err error, context Context) error {
	defer func() {
		if e := recover(); e != nil {
			err = promise.NewPanicError(e)
		}
	}()
	switch event := service.Event.(type) {
	case sendErrorEvent:
		event.OnSendError(err, context)
	case sendErrorEvent2:
		err = event.OnSendError(err, context)
	}
	return err
}

func (service *BaseService) sendError(err error, context Context) []byte {
	err = service.fireErrorEvent(err, context)
	w := io.NewWriter(true)
	w.WriteByte(io.TagError)
	w.WriteString(service.getErrorMessage(err))
	return w.Bytes()
}

func (service *BaseService) endError(err error, context Context) []byte {
	w := io.NewByteWriter(service.sendError(err, context))
	w.WriteByte(io.TagEnd)
	return w.Bytes()
}

func doOutput(
	args []reflect.Value,
	results []reflect.Value,
	context ServiceContext) []byte {
	method := context.Method
	w := io.NewWriter(method.Simple)
	switch method.Mode {
	case RawWithEndTag:
		return results[0].Bytes()
	case Raw:
		w.Write(results[0].Bytes())
	default:
		w.WriteByte(io.TagResult)
		if method.Mode == Serialized {
			w.Write(results[0].Bytes())
		} else {
			switch len(results) {
			case 0:
				w.WriteNil()
			case 1:
				w.WriteValue(results[0])
			default:
				w.WriteSlice(results)
			}
		}
		if context.ByRef {
			w.WriteByte(io.TagArgument)
			w.Reset()
			w.WriteSlice(args)
		}
	}
	return w.Bytes()
}

func (service *BaseService) fireBeforeInvokeEvent(
	name string, args []reflect.Value, context ServiceContext) error {
	switch event := service.Event.(type) {
	case beforeInvokeEvent:
		event.OnBeforeInvoke(name, args, context.ByRef, context)
	case beforeInvokeEvent2:
		return event.OnBeforeInvoke(name, args, context.ByRef, context)
	}
	return nil
}

func (service *BaseService) fireAfterInvokeEvent(
	name string, args []reflect.Value, results []reflect.Value, context ServiceContext) error {
	switch event := service.Event.(type) {
	case afterInvokeEvent:
		event.OnAfterInvoke(name, args, context.ByRef, results, context)
	case afterInvokeEvent2:
		return event.OnAfterInvoke(name, args, context.ByRef, results, context)
	}
	return nil
}

func (service *BaseService) beforeInvoke(
	name string, args []reflect.Value, context ServiceContext) promise.Promise {
	return promise.Sync(func() error {
		return service.fireBeforeInvokeEvent(name, args, context)
	}).Then(func() promise.Promise {
		return service.handlerManager.invokeHandler(name, args, context)
	}).Then(func(results []reflect.Value) ([]byte, error) {
		err := service.fireAfterInvokeEvent(name, args, results, context)
		if err != nil {
			return nil, err
		}
		return doOutput(args, results, context), nil
	})
}

func (service *BaseService) invokeHandler(
	name string, args []reflect.Value, context ServiceContext) promise.Promise {
	if context.Oneway {
		go func() {
			defer recover()
			service.callService(name, args, context)
		}()
		return promise.Resolve(nil)
	}
	return promise.Sync(func() ([]reflect.Value, error) {
		results := service.callService(name, args, context)
		n := len(results)
		if n == 0 {
			return results, nil
		}
		err, ok := results[n-1].Interface().(error)
		if ok {
			results = results[:n-1]
		}
		return results, err
	})
}
<commit_msg>Update service.go<commit_after>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * rpc/service.go                                         *
 *                                                        *
 * hprose service for Go.                                 *
 *                                                        *
 * LastModified: Sep 12, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

package rpc

import (
	"crypto/rand"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
	"unsafe"

	"github.com/hprose/hprose-golang/io"
	"github.com/hprose/hprose-golang/pool"
	"github.com/hprose/hprose-golang/promise"
)

// Service interface
type Service interface {
	AddFunction(name string, function interface{}, options MethodOptions) Service
	AddFunctions(names []string, functions []interface{}, options MethodOptions) Service
	AddMethod(name string, obj interface{}, options MethodOptions, alias ...string) Service
	AddMethods(names []string, obj interface{}, options MethodOptions, aliases ...[]string) Service
	AddInstanceMethods(obj interface{}, options MethodOptions) Service
	AddAllMethods(obj interface{}, options MethodOptions) Service
	AddMissingMethod(method MissingMethod, options MethodOptions) Service
	Remove(name string) Service
	Filter() Filter
	FilterByIndex(index int) Filter
	SetFilter(filter ...Filter) Service
	AddFilter(filter ...Filter) Service
	RemoveFilterByIndex(index int) Service
	RemoveFilter(filter ...Filter) Service
	AddInvokeHandler(handler InvokeHandler) Service
	AddBeforeFilterHandler(handler FilterHandler) Service
	AddAfterFilterHandler(handler FilterHandler) Service
}

type fixer interface {
	FixArguments(args []reflect.Value, context Context)
}

func fixArguments(args []reflect.Value, context Context) {
	i := len(args) - 1
	lastParamType := args[i].Type()
	typ := (*emptyInterface)(unsafe.Pointer(&lastParamType)).ptr
	if typ == interfaceType || typ == contextType {
		args[i] = reflect.ValueOf(context)
	}
}

// BaseService is the hprose base service
type BaseService struct {
	*methodManager
	*handlerManager
	*filterManager
	fixer
	Event      ServiceEvent
	Debug      bool
	Simple     bool
	Timeout    time.Duration
	Heartbeat  time.Duration
	ErrorDelay time.Duration
	allTopics  map[string]map[string]*topic
}

// GetNextID is the default method for client uid
func GetNextID() (uid string) {
	u := make([]byte, 16)
	rand.Read(u)
	u[6] = (u[6] & 0x0f) | 0x40
	u[8] = (u[8] & 0x3f) | 0x80
	uid = fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
	return
}

// NewBaseService is the constructor for BaseService
func NewBaseService() (service *BaseService) {
	service = new(BaseService)
	service.methodManager = newMethodManager()
	service.handlerManager = newHandlerManager()
	service.filterManager = &filterManager{}
	service.Timeout = 120 * 1000 * 1000
	service.Heartbeat = 3 * 1000 * 1000
	service.ErrorDelay = 10 * 1000 * 1000
	service.allTopics = make(map[string]map[string]*topic)
	service.AddFunction("#", GetNextID, MethodOptions{Simple: true})
	service.override.invokeHandler = func(
		name string, args []reflect.Value, context Context) promise.Promise {
		return service.invoke(name, args, context.(ServiceContext))
	}
	service.override.beforeFilterHandler = func(
		request []byte, context Context) promise.Promise {
		return service.beforeFilter(request, context.(ServiceContext))
	}
	service.override.afterFilterHandler = func(
		request []byte, context Context) promise.Promise {
		return service.afterFilter(request, context.(ServiceContext))
	}
	return service
}

// AddFunction publish a func or bound method
// name is the method name
// function is a func or bound method
// options includes Mode, Simple, Oneway and NameSpace
func (service *BaseService) AddFunction(name string, function interface{}, options MethodOptions) Service {
	service.methodManager.AddFunction(name, function, options)
	return service
}

// AddFunctions is used for batch publishing service method
func (service *BaseService) AddFunctions(names []string, functions []interface{}, options MethodOptions) Service {
	service.methodManager.AddFunctions(names, functions, options)
	return service
}

// AddMethod is used for publishing a method on the obj with an alias
func (service *BaseService) AddMethod(name string, obj interface{}, options MethodOptions, alias ...string) Service {
	service.methodManager.AddMethod(name, obj, options, alias...)
	return service
}

// AddMethods is used for batch publishing methods on the obj with aliases
func (service *BaseService) AddMethods(names []string, obj interface{}, options MethodOptions, aliases ...[]string) Service {
	service.methodManager.AddMethods(names, obj, options, aliases...)
	return service
}

// AddInstanceMethods is used for publishing all the public methods and func fields with options.
func (service *BaseService) AddInstanceMethods(obj interface{}, options MethodOptions) Service {
	service.methodManager.AddInstanceMethods(obj, options)
	return service
}

// AddAllMethods will publish all methods and non-nil function fields on the
// obj self and on its anonymous or non-anonymous struct fields (or pointer to
// pointer ... to pointer struct fields). This is a recursive operation.
// So it's a pit, if you do not know what you are doing, do not step on.
func (service *BaseService) AddAllMethods(obj interface{}, options MethodOptions) Service {
	service.methodManager.AddAllMethods(obj, options)
	return service
}

// AddMissingMethod is used for publishing a method,
// all methods not explicitly published will be redirected to this method.
func (service *BaseService) AddMissingMethod(method MissingMethod, options MethodOptions) Service {
	service.methodManager.AddMissingMethod(method, options)
	return service
}

// Remove the published func or method by name
func (service *BaseService) Remove(name string) Service {
	service.methodManager.Remove(name)
	return service
}

// Filter return the first filter
func (service *BaseService) Filter() Filter {
	return service.filterManager.Filter()
}

// FilterByIndex return the filter by index
func (service *BaseService) FilterByIndex(index int) Filter {
	return service.filterManager.FilterByIndex(index)
}

// SetFilter will replace the current filter settings
func (service *BaseService) SetFilter(filter ...Filter) Service {
	service.filterManager.SetFilter(filter...)
	return service
}

// AddFilter add the filter to this Service
func (service *BaseService) AddFilter(filter ...Filter) Service {
	service.filterManager.AddFilter(filter...)
	return service
}

// RemoveFilterByIndex remove the filter by the index
func (service *BaseService) RemoveFilterByIndex(index int) Service {
	service.filterManager.RemoveFilterByIndex(index)
	return service
}

// RemoveFilter remove the filter from this Service
func (service *BaseService) RemoveFilter(filter ...Filter) Service {
	service.filterManager.RemoveFilter(filter...)
	return service
}

// AddInvokeHandler add the invoke handler to this Service
func (service *BaseService) AddInvokeHandler(handler InvokeHandler) Service {
	service.handlerManager.AddInvokeHandler(handler)
	return service
}

// AddBeforeFilterHandler add the filter handler before filters
func (service *BaseService) AddBeforeFilterHandler(handler FilterHandler) Service {
	service.handlerManager.AddBeforeFilterHandler(handler)
	return service
}

// AddAfterFilterHandler add the filter handler after filters
func (service *BaseService) AddAfterFilterHandler(handler FilterHandler) Service {
	service.handlerManager.AddAfterFilterHandler(handler)
	return service
}

func (service *BaseService) callService(
	name string, args []reflect.Value, context ServiceContext) []reflect.Value {
	remoteMethod := context.Method
	function := remoteMethod.Function
	if context.IsMissingMethod {
		missingMethod := function.Interface().(MissingMethod)
		return missingMethod(name, args, context)
	}
	ft := function.Type()
	if ft.IsVariadic() {
		return function.CallSlice(args)
	}
	count := len(args)
	n := ft.NumIn()
	if n < count {
		args = args[:n]
	}
	return function.Call(args)
}

func (service *BaseService) getErrorMessage(err error) string {
	if panicError, ok := err.(*promise.PanicError); ok {
		if service.Debug {
			return fmt.Sprintf("%v\r\n%s", panicError.Panic, panicError.Stack)
		}
		return panicError.Error()
	}
	return err.Error()
}

func (service *BaseService) fireErrorEvent(
	e error, context Context) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = promise.NewPanicError(e)
		}
	}()
	err = e
	switch event := service.Event.(type) {
	case sendErrorEvent:
		event.OnSendError(err, context)
	case sendErrorEvent2:
		err = event.OnSendError(err, context)
	}
	return err
}

func (service *BaseService) sendError(err error, context Context) []byte {
	err = service.fireErrorEvent(err, context)
	w := io.NewWriter(true)
	w.WriteByte(io.TagError)
	w.WriteString(service.getErrorMessage(err))
	return w.Bytes()
}

func (service *BaseService) endError(err error, context Context) []byte {
	w := io.NewByteWriter(service.sendError(err, context))
	w.WriteByte(io.TagEnd)
	return w.Bytes()
}

func doOutput(
	args []reflect.Value,
	results []reflect.Value,
	context ServiceContext) []byte {
	method := context.Method
	writer := io.NewWriter(method.Simple)
	switch method.Mode {
	case RawWithEndTag:
		return results[0].Bytes()
	case Raw:
		writer.Write(results[0].Bytes())
	default:
		writer.WriteByte(io.TagResult)
		if method.Mode == Serialized {
			writer.Write(results[0].Bytes())
		} else {
			switch len(results) {
			case 0:
				writer.WriteNil()
			case 1:
				writer.WriteValue(results[0])
			default:
				writer.WriteSlice(results)
			}
		}
		if context.ByRef {
			writer.WriteByte(io.TagArgument)
			writer.Reset()
			writer.WriteSlice(args)
		}
	}
	return writer.Bytes()
}

func (service *BaseService) fireBeforeInvokeEvent(
	name string, args []reflect.Value, context ServiceContext) error {
	switch event := service.Event.(type) {
	case beforeInvokeEvent:
		event.OnBeforeInvoke(name, args, context.ByRef, context)
	case beforeInvokeEvent2:
		return event.OnBeforeInvoke(name, args, context.ByRef, context)
	}
	return nil
}

func (service *BaseService) fireAfterInvokeEvent(
	name string, args []reflect.Value, results []reflect.Value, context ServiceContext) error {
	switch event := service.Event.(type) {
	case afterInvokeEvent:
		event.OnAfterInvoke(name, args, context.ByRef, results, context)
	case afterInvokeEvent2:
		return event.OnAfterInvoke(name, args, context.ByRef, results, context)
	}
	return nil
}

func (service *BaseService) beforeInvoke(
	name string, args []reflect.Value, context ServiceContext) promise.Promise {
	return promise.Sync(func() error {
		return service.fireBeforeInvokeEvent(name, args, context)
	}).Then(func() promise.Promise {
		return service.handlerManager.invokeHandler(name, args, context)
	}).Then(func(results []reflect.Value) ([]byte, error) {
		err := service.fireAfterInvokeEvent(name, args, results, context)
		if err != nil {
			return nil, err
		}
		return doOutput(args, results, context), nil
	})
}

func (service *BaseService) invoke(
	name string, args []reflect.Value, context ServiceContext) promise.Promise {
	if context.Oneway {
		go func() {
			defer recover()
			service.callService(name, args, context)
		}()
		return promise.Resolve(nil)
	}
	return promise.Sync(func() ([]reflect.Value, error) {
		results := service.callService(name, args, context)
		n := len(results)
		if n == 0 {
			return results, nil
		}
		err, ok := results[n-1].Interface().(error)
		if ok {
			results = results[:n-1]
		}
		return results, err
	})
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

var interfaceType = reflect.TypeOf((interface{})(nil))

func (service *BaseService) readArguments(
	reader *io.Reader,
	method *Method,
	context ServiceContext) (args []reflect.Value) {
	if method == nil {
		return reader.ReadSliceWithoutTag()
	}
	count := reader.ReadCount()
	ft := method.Function.Type()
	n := ft.NumIn()
	if ft.IsVariadic() {
		n--
	}
	max := max(n, count)
	args = make([]reflect.Value, max)
	for i := 0; i < n; i++ {
		args[i] = reflect.New(ft.In(i)).Elem()
	}
	if n < count {
		for i := n; i < count; i++ {
			if ft.IsVariadic() {
				args[i] = reflect.New(ft.In(n)).Elem()
			} else {
				args[i] = reflect.New(interfaceType).Elem()
			}
		}
	}
	reader.ReadSlice(args[:count])
	if !ft.IsVariadic() && n > count {
		service.FixArguments(args, context)
	}
	return
}

func (service *BaseService) doInvoke(
	reader *io.Reader, context ServiceContext) promise.Promise {
	var results []interface{}
	for {
		reader.Reset()
		name := reader.ReadString()
		alias := strings.ToLower(name)
		method := service.RemoteMethods[alias]
		tag := reader.CheckTags([]byte{io.TagList, io.TagEnd, io.TagCall})
		var args []reflect.Value
		if tag == io.TagList {
			reader.Reset()
			args = service.readArguments(reader, method, context)
			tag = reader.CheckTags([]byte{io.TagTrue, io.TagEnd, io.TagCall})
			if tag == io.TagTrue {
				context.ByRef = true
				tag = reader.CheckTags([]byte{io.TagEnd, io.TagCall})
			}
		}
		if method == nil {
			method = service.RemoteMethods["*"]
			context.IsMissingMethod = true
		}
		if method == nil {
			err := errors.New("Can't find this method " + name)
			results = append(results, service.sendError(err, context))
		} else {
			context.Method = method
			results = append(results, service.beforeInvoke(name, args, context))
		}
		if tag != io.TagCall {
			break
		}
	}
	return promise.All(results...).Then(func(results []interface{}) []byte {
		writer := &io.ByteWriter{}
		n := len(results)
		for i := 0; i < n; i++ {
			data := results[i].([]byte)
			writer.Write(data)
			pool.Recycle(data)
		}
		writer.WriteByte(io.TagEnd)
		return writer.Bytes()
	})
}

func (service *BaseService) doFunctionList(context ServiceContext) []byte {
	writer := io.NewWriter(true)
	writer.WriteByte(io.TagFunctions)
	writer.WriteStringSlice(service.MethodNames)
	writer.WriteByte(io.TagEnd)
	return writer.Bytes()
}

func (service *BaseService) afterFilter(
	request []byte, context ServiceContext) promise.Promise {
	reader := io.NewReader(request, false)
	tag, err := reader.ReadByte()
	if err != nil {
		return promise.Reject(err)
	}
	switch tag {
	case io.TagCall:
		return service.doInvoke(reader, context)
	case io.TagEnd:
		return promise.Resolve(service.doFunctionList(context))
	default:
		return promise.Reject(fmt.Errorf("Wrong Request: \r\n%s", request))
	}
}

func (service *BaseService) delayError(err error, context ServiceContext) promise.Promise {
	e := service.endError(err, context)
	if service.ErrorDelay > 0 {
		return promise.Delayed(service.ErrorDelay, e)
	}
	return promise.Resolve(e)
}

func (service *BaseService) beforeFilter(
	request []byte, context ServiceContext) promise.Promise {
	return promise.
		Sync(func() promise.Promise {
			request = service.inputFilter(request, context)
			return service.afterFilterHandler(request, context)
		}).
		Catch(func(err error) promise.Promise {
			return service.delayError(err, context)
		}).
		Then(func(response []byte) []byte {
			return service.outputFilter(response, context)
		})
}

func (service *BaseService) handler(
	request []byte, context ServiceContext) promise.Promise {
	return service.beforeFilterHandler(request, context)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Janoš Guljaš <janos@resenje.org>
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package server

import (
	"context"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"

	"gopkg.in/throttled/throttled.v2/store/memstore"
	"resenje.org/logging"
	"resenje.org/recovery"
	"resenje.org/web/maintenance"

	"gopherpit.com/gopherpit/server/config"
	"gopherpit.com/gopherpit/services/key/bolt"
	"gopherpit.com/gopherpit/services/packages/bolt"
	"gopherpit.com/gopherpit/services/session/bolt"
	"gopherpit.com/gopherpit/services/user/bolt"
)

// testServerOptions encapsulates all options needed for server and services
// for server to run for testing. It provides a convenient methods to
// update it's fields.
type testServerOptions struct {
	Options
}

func (o *testServerOptions) set(field string, value interface{}) (isSet bool) {
	f := reflect.ValueOf(o).Elem().FieldByName(field)
	if !f.IsValid() {
		return
	}
	if !f.CanSet() {
		return
	}
	v := reflect.ValueOf(value)
	if f.Kind() != v.Kind() {
		return
	}
	f.Set(v)
	return true
}

func (o *testServerOptions) update(values map[string]interface{}) (notSet []string) {
	for field, value := range values {
		if isSet := o.set(field, value); !isSet {
			notSet = append(notSet, field)
		}
	}
	return
}

// Email recorder is a simple resenje.org/email.Service implementation that
// records values from the latest sent email message.
type emailRecorder struct {
	DefaultFrom     string
	NotifyAddresses []string
	SubjectPrefix   string

	From    string
	To      []string
	Subject string
	Body    string
}

func (r emailRecorder) SendEmail(from string, to []string, subject string, body string) error {
	r.From = from
	r.To = to
	r.Subject = subject
	r.Body = body
	return nil
}

func (r emailRecorder) Notify(subject, body string) error {
	if len(r.NotifyAddresses) == 0 {
		r.From = ""
		r.To = nil
		r.Subject = ""
		r.Body = ""
		return nil
	}
	return r.SendEmail(r.DefaultFrom, r.NotifyAddresses, r.SubjectPrefix+subject, body)
}

// newTestServer requires stopping it:
//
// Example:
//
//     newTestServer(nil)
//     defer s.stopTestServer()
//
func newTestServer(options map[string]interface{}) (*Server, error) {
	logger, err := logging.GetLogger("default")
	if err != nil {
		return nil, fmt.Errorf("get default logger: %s", err)
	}

	storageDir, err := ioutil.TempDir("", "gopherpit-test-")
	if err != nil {
		return nil, fmt.Errorf("temp storage dir: %s", err)
	}

	emailService := &emailRecorder{
		DefaultFrom:     "gopherpit-test@localhost",
		NotifyAddresses: []string{"gopherpit@localhost"},
		SubjectPrefix:   "GopherPitTest",
	}
	recoveryService := &recovery.Service{
		Version:   config.Version,
		BuildInfo: config.BuildInfo,
		LogFunc:   logging.Error,
		Notifier:  emailService,
	}

	sessionDB, err := boltSession.NewDB(filepath.Join(storageDir, "session.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("session service bolt database: %s", err)
	}
	sessionService := &boltSession.Service{
		DB:              sessionDB,
		DefaultLifetime: 45 * 24 * time.Hour,
		CleanupPeriod:   0,
		Logger:          logger,
	}
	userDB, err := boltUser.NewDB(filepath.Join(storageDir, "user.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("user service bolt database: %s", err)
	}
	userService := &boltUser.Service{
		DB: userDB,
		PasswordNoReuseMonths: 0,
		Logger:                logger,
	}
	packagesDB, err := boltPackages.NewDB(filepath.Join(storageDir, "packages.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("packages service bolt database: %s", err)
	}
	packagesChangelog, err := boltPackages.NewChangelogPool(filepath.Join(storageDir, "changelog"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("packages service bolt changelog database pool: %s", err)
	}
	packagesService := &boltPackages.Service{
		DB:        packagesDB,
		Changelog: packagesChangelog,
		Logger:    logger,
	}
	keyDB, err := boltKey.NewDB(filepath.Join(storageDir, "key.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("key service bolt database: %s", err)
	}
	keyService := &boltKey.Service{
		DB:     keyDB,
		Logger: logger,
	}
	gcraStoreService, err := memstore.New(65536)
	if err != nil {
		return nil, fmt.Errorf("gcra memstore: %s", err)
	}

	o := testServerOptions{
		Options: Options{
			Name:                    config.Name,
			Version:                 "0",
			BuildInfo:               "test",
			Brand:                   "GopherPit",
			Listen:                  "127.0.0.1:",
			ListenTLS:               "",
			ListenInternal:          "",
			ListenInternalTLS:       "",
			TLSKey:                  "",
			TLSCert:                 "",
			Domain:                  "localhost",
			Headers:                 map[string]string{},
			SessionCookieName:       "testsesid",
			AssetsDir:               filepath.Join(build.Default.GOPATH, "src", "gopherpit.com", "gopherpit", "assets"),
			StaticDir:               filepath.Join(build.Default.GOPATH, "src", "gopherpit.com", "gopherpit", "static"),
			TemplatesDir:            filepath.Join(build.Default.GOPATH, "src", "gopherpit.com", "gopherpit", "templates"),
			StorageDir:              "",
			GoogleAnalyticsID:       "",
			RememberMeDays:          45,
			DefaultFrom:             "gopherpit-test@localhost",
			ContactRecipientEmail:   "gopherpit-test@localhost",
			ACMEDirectoryURL:        "",
			ACMEDirectoryURLStaging: "",
			SkipDomainVerification:  false,
			VerificationSubdomain:   "_gopherpit",
			TrustedDomains: []string{
				"trusted.com",
			},
			ForbiddenDomains: []string{
				"forbidden.com",
			},
			APITrustedProxyCIDRs: []string{},
			APIProxyRealIPHeader: "X-Real-Ip",
			APIHourlyRateLimit:   0,
			APIEnabled:           true,

			Logger:              logger,
			AccessLogger:        logger,
			AuditLogger:         logger,
			PackageAccessLogger: logger,

			EmailService:       emailService,
			RecoveryService:    recoveryService,
			MaintenanceService: maintenance.New(),

			SessionService:      sessionService,
			UserService:         userService,
			NotificationService: nil,
			CertificateService:  nil,
			PackagesService:     packagesService,
			KeyService:          keyService,
			GCRAStoreService:    gcraStoreService,
		},
	}

	if notSet := o.update(options); notSet != nil {
		return nil, fmt.Errorf("options not set: %s", strings.Join(notSet, ", "))
	}

	// StorageDir must be set explicitly by overriding any configured
	// path as it is removed in s.stopTestServer().
	o.StorageDir = storageDir

	s, err := New(o.Options)
	if err != nil {
		return nil, fmt.Errorf("configure: %s", err)
	}

	if err := s.Serve(); err != nil {
		return nil, fmt.Errorf("serve %s", err)
	}

	return s, nil
}

// s.stopTestServer must be called to shut down HTTP servers and remove storage dir.
func (s *Server) stopTestServer() {
	storageDir := s.StorageDir
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	s.Shutdown(ctx)
	cancel()
	if storageDir != "" {
		if err := os.RemoveAll(storageDir); err != nil {
			panic(fmt.Errorf("remove %s: %s", storageDir, err))
		}
	}
}

func TestVersionFunc(t *testing.T) {
	t.Run("no version", func(t *testing.T) {
		Version := "0"
		s, err := newTestServer(map[string]interface{}{
			"Version":   "",
			"BuildInfo": "",
		})
		if err != nil {
			t.Fatal(err)
		}
		defer s.stopTestServer()

		v := s.version()
		if v != Version {
			t.Errorf("expected %q, got %q", Version, v)
		}
	})
	t.Run("without build info", func(t *testing.T) {
		Version := "1.25.84"
		s, err := newTestServer(map[string]interface{}{
			"Version":   Version,
			"BuildInfo": "",
		})
		if err != nil {
			t.Fatal(err)
		}
		defer s.stopTestServer()

		v := s.version()
		if v != Version {
			t.Errorf("expected %q, got %q", Version, v)
		}
	})
	t.Run("with build info", func(t *testing.T) {
		Version := "1.25.84-123456"
		s, err := newTestServer(map[string]interface{}{
			"Version":   "1.25.84",
			"BuildInfo": "123456",
		})
		if err != nil {
			t.Fatal(err)
		}
		defer s.stopTestServer()

		v := s.version()
		if v != Version {
			t.Errorf("expected %q, got %q", Version, v)
		}
	})
}
<commit_msg>Fix testServerOptions<commit_after>// Copyright (c) 2017, Janoš Guljaš <janos@resenje.org>
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package server

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"

	"gopkg.in/throttled/throttled.v2/store/memstore"
	"resenje.org/logging"
	"resenje.org/recovery"
	"resenje.org/web/maintenance"

	"gopherpit.com/gopherpit/server/config"
	"gopherpit.com/gopherpit/services/key/bolt"
	"gopherpit.com/gopherpit/services/packages/bolt"
	"gopherpit.com/gopherpit/services/session/bolt"
	"gopherpit.com/gopherpit/services/user/bolt"
)

// testServerOptions encapsulates all options needed for server and services
// for server to run for testing. It provides a convenient methods to
// update it's fields.
type testServerOptions struct {
	Options
}

func (o *testServerOptions) set(field string, value interface{}) (isSet bool) {
	f := reflect.ValueOf(o).Elem().FieldByName(field)
	if !f.IsValid() {
		return
	}
	if !f.CanSet() {
		return
	}
	v := reflect.ValueOf(value)
	if f.Kind() != v.Kind() {
		return
	}
	f.Set(v)
	return true
}

func (o *testServerOptions) update(values map[string]interface{}) (notSet []string) {
	for field, value := range values {
		if isSet := o.set(field, value); !isSet {
			notSet = append(notSet, field)
		}
	}
	return
}

// Email recorder is a simple resenje.org/email.Service implementation that
// records values from the latest sent email message.
type emailRecorder struct {
	DefaultFrom     string
	NotifyAddresses []string
	SubjectPrefix   string

	From    string
	To      []string
	Subject string
	Body    string
}

func (r emailRecorder) SendEmail(from string, to []string, subject string, body string) error {
	r.From = from
	r.To = to
	r.Subject = subject
	r.Body = body
	return nil
}

func (r emailRecorder) Notify(subject, body string) error {
	if len(r.NotifyAddresses) == 0 {
		r.From = ""
		r.To = nil
		r.Subject = ""
		r.Body = ""
		return nil
	}
	return r.SendEmail(r.DefaultFrom, r.NotifyAddresses, r.SubjectPrefix+subject, body)
}

// newTestServer requires stopping it:
//
// Example:
//
//     newTestServer(nil)
//     defer s.stopTestServer()
//
func newTestServer(options map[string]interface{}) (*Server, error) {
	logger, err := logging.GetLogger("default")
	if err != nil {
		return nil, fmt.Errorf("get default logger: %s", err)
	}

	storageDir, err := ioutil.TempDir("", "gopherpit-test-")
	if err != nil {
		return nil, fmt.Errorf("temp storage dir: %s", err)
	}

	emailService := &emailRecorder{
		DefaultFrom:     "gopherpit-test@localhost",
		NotifyAddresses: []string{"gopherpit@localhost"},
		SubjectPrefix:   "GopherPitTest",
	}
	recoveryService := &recovery.Service{
		Version:   config.Version,
		BuildInfo: config.BuildInfo,
		LogFunc:   logging.Error,
		Notifier:  emailService,
	}

	sessionDB, err := boltSession.NewDB(filepath.Join(storageDir, "session.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("session service bolt database: %s", err)
	}
	sessionService := &boltSession.Service{
		DB:              sessionDB,
		DefaultLifetime: 45 * 24 * time.Hour,
		CleanupPeriod:   0,
		Logger:          logger,
	}
	userDB, err := boltUser.NewDB(filepath.Join(storageDir, "user.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("user service bolt database: %s", err)
	}
	userService := &boltUser.Service{
		DB: userDB,
		PasswordNoReuseMonths: 0,
		Logger:                logger,
	}
	packagesDB, err := boltPackages.NewDB(filepath.Join(storageDir, "packages.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("packages service bolt database: %s", err)
	}
	packagesChangelog, err := boltPackages.NewChangelogPool(filepath.Join(storageDir, "changelog"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("packages service bolt changelog database pool: %s", err)
	}
	packagesService := &boltPackages.Service{
		DB:        packagesDB,
		Changelog: packagesChangelog,
		Logger:    logger,
	}
	keyDB, err := boltKey.NewDB(filepath.Join(storageDir, "key.db"), 0666, nil)
	if err != nil {
		return nil, fmt.Errorf("key service bolt database: %s", err)
	}
	keyService := &boltKey.Service{
		DB:     keyDB,
		Logger: logger,
	}
	gcraStoreService, err := memstore.New(65536)
	if err != nil {
		return nil, fmt.Errorf("gcra memstore: %s", err)
	}

	o := testServerOptions{
		Options: Options{
			Name:                    config.Name,
			Version:                 "0",
			BuildInfo:               "test",
			Brand:                   "GopherPit",
			Listen:                  "127.0.0.1:",
			ListenTLS:               "",
			ListenInternal:          "",
			ListenInternalTLS:       "",
			TLSKey:                  "",
			TLSCert:                 "",
			Domain:                  "localhost",
			Headers:                 map[string]string{},
			SessionCookieName:       "testsesid",
			StorageDir:              "",
			GoogleAnalyticsID:       "",
			RememberMeDays:          45,
			DefaultFrom:             "gopherpit-test@localhost",
			ContactRecipientEmail:   "gopherpit-test@localhost",
			ACMEDirectoryURL:        "",
			ACMEDirectoryURLStaging: "",
			SkipDomainVerification:  false,
			VerificationSubdomain:   "_gopherpit",
			TrustedDomains: []string{
				"trusted.com",
			},
			ForbiddenDomains: []string{
				"forbidden.com",
			},
			APITrustedProxyCIDRs: []string{},
			APIProxyRealIPHeader: "X-Real-Ip",
			APIHourlyRateLimit:   0,
			APIEnabled:           true,

			Logger:              logger,
			AccessLogger:        logger,
			AuditLogger:         logger,
			PackageAccessLogger: logger,

			EmailService:       emailService,
			RecoveryService:    recoveryService,
			MaintenanceService: maintenance.New(),

			SessionService:      sessionService,
			UserService:         userService,
			NotificationService: nil,
			CertificateService:  nil,
			PackagesService:     packagesService,
			KeyService:          keyService,
			GCRAStoreService:    gcraStoreService,
		},
	}

	if notSet := o.update(options); notSet != nil {
		return nil, fmt.Errorf("options not set: %s", strings.Join(notSet, ", "))
	}

	// StorageDir must be set explicitly by overriding any configured
	// path as it is removed in s.stopTestServer().
	o.StorageDir = storageDir

	s, err := New(o.Options)
	if err != nil {
		return nil, fmt.Errorf("configure: %s", err)
	}

	if err := s.Serve(); err != nil {
		return nil, fmt.Errorf("serve %s", err)
	}

	return s, nil
}

// s.stopTestServer must be called to shut down HTTP servers and remove storage dir.
func (s *Server) stopTestServer() {
	storageDir := s.StorageDir
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	s.Shutdown(ctx)
	cancel()
	if storageDir != "" {
		if err := os.RemoveAll(storageDir); err != nil {
			panic(fmt.Errorf("remove %s: %s", storageDir, err))
		}
	}
}

func TestVersionFunc(t *testing.T) {
	t.Run("no version", func(t *testing.T) {
		Version := "0"
		s, err := newTestServer(map[string]interface{}{
			"Version":   "",
			"BuildInfo": "",
		})
		if err != nil {
			t.Fatal(err)
		}
		defer s.stopTestServer()

		v := s.version()
		if v != Version {
			t.Errorf("expected %q, got %q", Version, v)
		}
	})
	t.Run("without build info", func(t *testing.T) {
		Version := "1.25.84"
		s, err := newTestServer(map[string]interface{}{
			"Version":   Version,
			"BuildInfo": "",
		})
		if err != nil {
			t.Fatal(err)
		}
		defer s.stopTestServer()

		v := s.version()
		if v != Version {
			t.Errorf("expected %q, got %q", Version, v)
		}
	})
	t.Run("with build info", func(t *testing.T) {
		Version := "1.25.84-123456"
		s, err := newTestServer(map[string]interface{}{
			"Version":   "1.25.84",
			"BuildInfo": "123456",
		})
		if err != nil {
			t.Fatal(err)
		}
		defer s.stopTestServer()

		v := s.version()
		if v != Version {
			t.Errorf("expected %q, got %q", Version, v)
		}
	})
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package golang

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/golang/glog"

	"k8s.io/publishing-bot/cmd/publishing-bot/config"
)

const defaultGoVersion = "1.12.8"

// installGoVersions download and unpacks the specified Golang versions to $GOPATH/
func InstallDefaultGoVersion() error {
	var empty config.RepositoryRules
	return InstallGoVersions(&empty)
}

// installGoVersions download and unpacks the specified Golang versions to $GOPATH/
func InstallGoVersions(rules *config.RepositoryRules) error {
	goVersions := []string{defaultGoVersion}
	for _, rule := range rules.Rules {
		for _, branch := range rule.Branches {
			if branch.GoVersion != "" {
				found := false
				for _, v := range goVersions {
					if v == branch.GoVersion {
						found = true
					}
				}
				if !found {
					goVersions = append(goVersions, branch.GoVersion)
				}
			}
		}
	}
	systemGoPath := os.Getenv("GOPATH")
	for _, v := range goVersions {
		if err := installGoVersion(v, filepath.Join(systemGoPath, "go-"+v)); err != nil {
			return err
		}
	}
	goLink, target := filepath.Join(systemGoPath, "go"), filepath.Join(systemGoPath, "go-"+defaultGoVersion)
	os.Remove(goLink)
	if err := os.Symlink(target, goLink); err != nil {
		return fmt.Errorf("failed to link %s to %s: %s", goLink, target, err)
	}

	return nil
}

func installGoVersion(v string, pth string) error {
	if s, err := os.Stat(pth); err != nil && !os.IsNotExist(err) {
		return err
	} else if err == nil {
		if s.IsDir() {
			glog.Infof("Found existing go %s at %s", v, pth)
			return nil
		}
		return fmt.Errorf("expected %s to be a directory", pth)
	}

	glog.Infof("Installing go %s to %s", v, pth)
	tmpPath, err := ioutil.TempDir(os.Getenv("GOPATH"), "go-tmp-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("curl -SLf https://storage.googleapis.com/golang/go%s.linux-amd64.tar.gz | tar -xz --strip 1 -C %s", v, tmpPath))
	cmd.Dir = tmpPath
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("command %q failed: %v", strings.Join(cmd.Args, " "), err)
	}
	if err := os.Rename(tmpPath, pth); err != nil {
		return err
	}

	return nil
}
<commit_msg>Bump default Go version to 1.12.9<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package golang

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/golang/glog"

	"k8s.io/publishing-bot/cmd/publishing-bot/config"
)

const defaultGoVersion = "1.12.9"

// installGoVersions download and unpacks the specified Golang versions to $GOPATH/
func InstallDefaultGoVersion() error {
	var empty config.RepositoryRules
	return InstallGoVersions(&empty)
}

// installGoVersions download and unpacks the specified Golang versions to $GOPATH/
func InstallGoVersions(rules *config.RepositoryRules) error {
	goVersions := []string{defaultGoVersion}
	for _, rule := range rules.Rules {
		for _, branch := range rule.Branches {
			if branch.GoVersion != "" {
				found := false
				for _, v := range goVersions {
					if v == branch.GoVersion {
						found = true
					}
				}
				if !found {
					goVersions = append(goVersions, branch.GoVersion)
				}
			}
		}
	}
	systemGoPath := os.Getenv("GOPATH")
	for _, v := range goVersions {
		if err := installGoVersion(v, filepath.Join(systemGoPath, "go-"+v)); err != nil {
			return err
		}
	}
	goLink, target := filepath.Join(systemGoPath, "go"), filepath.Join(systemGoPath, "go-"+defaultGoVersion)
	os.Remove(goLink)
	if err := os.Symlink(target, goLink); err != nil {
		return fmt.Errorf("failed to link %s to %s: %s", goLink, target, err)
	}

	return nil
}

func installGoVersion(v string, pth string) error {
	if s, err := os.Stat(pth); err != nil && !os.IsNotExist(err) {
		return err
	} else if err == nil {
		if s.IsDir() {
			glog.Infof("Found existing go %s at %s", v, pth)
			return nil
		}
		return fmt.Errorf("expected %s to be a directory", pth)
	}

	glog.Infof("Installing go %s to %s", v, pth)
	tmpPath, err := ioutil.TempDir(os.Getenv("GOPATH"), "go-tmp-")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("curl -SLf https://storage.googleapis.com/golang/go%s.linux-amd64.tar.gz | tar -xz --strip 1 -C %s", v, tmpPath))
	cmd.Dir = tmpPath
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("command %q failed: %v", strings.Join(cmd.Args, " "), err)
	}
	if err := os.Rename(tmpPath, pth); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package server_test

// TODO: review the http testing
import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/mch1307/gomotics/nhc"
	. "github.com/mch1307/gomotics/server"
	"github.com/mch1307/gomotics/testutil"
	"github.com/mch1307/gomotics/types"
)

var baseUrl string

//const healthMsg = `{"alive":true}`

func init() {
	fmt.Println("starting server test")
	baseUrl = "http://" + testutil.ConnectHost + ":8081"
	testutil.InitStubNHC()
}

func TestHealth(t *testing.T) {
	req, err := http.NewRequest("GET", baseUrl+"/health", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(Health)
	handler.ServeHTTP(rr, req)
	if rr.Body.String() != HealthMsg {
		t.Errorf("health test failed: got %v, expect: %v", rr.Body.String(), HealthMsg)
	}

}

func Test_getNhcItem(t *testing.T) {
	req, err := http.NewRequest("GET", baseUrl+"/api/v1/nhc/99", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(nhc.GetNhcItem)
	handler.ServeHTTP(rr, req)
	expected := "light"
	var res types.Item
	json.Unmarshal(rr.Body.Bytes(), &res)
	if res.Name != expected {
		t.Errorf("getNhcItem failed: got %v, expect: %v", res, expected)
	}
}

func Test_getNhcItems(t *testing.T) {
	req, err := http.NewRequest("GET", baseUrl+"/api/v1/nhc", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(nhc.GetNhcItems)
	handler.ServeHTTP(rr, req)
	var found bool
	expected := "light"
	var res []types.Item
	json.Unmarshal(rr.Body.Bytes(), &res)
	for _, val := range res {
		if val.ID == 0 {
			if val.Name == expected {
				found = true
			}
		}
	}
	if !found {
		t.Error("GetNhcItems failed, expected light record not found")
	}
}

func Test_nhcCmd(t *testing.T) {
	expected := "Success"
	url := baseUrl + "/api/v1/nhc/action?id=1&value=100"
	hCli := http.Client{
		Timeout: time.Second * 5,
	}
	req, err := http.NewRequest(http.MethodPut, url, nil)
	if err != nil {
		t.Fatal(err)
	}
	//	req.Header.Set("User-Agent", "Test_nhcCmd")
	rsp, getErr := hCli.Do(req)
	if getErr != nil {
		fmt.Println(err)
	}
	got, readErr := ioutil.ReadAll(rsp.Body)
	if readErr != nil {
		fmt.Println("Read err: ", readErr)
	}
	//defer rsp.Body.Close()
	if string(got) != expected {
		t.Errorf("Test_nhcCmd failed, expecting %v, got %v", expected, string(got))
	}

}
<commit_msg>comment test_nhccmd<commit_after>package server_test

// TODO: review the http testing
import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/mch1307/gomotics/nhc"
	. "github.com/mch1307/gomotics/server"
	"github.com/mch1307/gomotics/testutil"
	"github.com/mch1307/gomotics/types"
)

var baseUrl string

//const healthMsg = `{"alive":true}`

func init() {
	fmt.Println("starting server test")
	baseUrl = "http://" + testutil.ConnectHost + ":8081"
	testutil.InitStubNHC()
}

func TestHealth(t *testing.T) {
	req, err := http.NewRequest("GET", baseUrl+"/health", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(Health)
	handler.ServeHTTP(rr, req)
	if rr.Body.String() != HealthMsg {
		t.Errorf("health test failed: got %v, expect: %v", rr.Body.String(), HealthMsg)
	}

}

func Test_getNhcItem(t *testing.T) {
	req, err := http.NewRequest("GET", baseUrl+"/api/v1/nhc/99", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(nhc.GetNhcItem)
	handler.ServeHTTP(rr, req)
	expected := "light"
	var res types.Item
	json.Unmarshal(rr.Body.Bytes(), &res)
	if res.Name != expected {
		t.Errorf("getNhcItem failed: got %v, expect: %v", res, expected)
	}
}

func Test_getNhcItems(t *testing.T) {
	req, err := http.NewRequest("GET", baseUrl+"/api/v1/nhc", nil)
	if err != nil {
		t.Fatal(err)
	}
	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(nhc.GetNhcItems)
	handler.ServeHTTP(rr, req)
	var found bool
	expected := "light"
	var res []types.Item
	json.Unmarshal(rr.Body.Bytes(), &res)
	for _, val := range res {
		if val.ID == 0 {
			if val.Name == expected {
				found = true
			}
		}
	}
	if !found {
		t.Error("GetNhcItems failed, expected light record not found")
	}
}

/* func Test_nhcCmd(t *testing.T) {
	expected := "Success"
	url := baseUrl + "/api/v1/nhc/action?id=1&value=100"
	hCli := http.Client{
		Timeout: time.Second * 2,
	}
	req, err := http.NewRequest(http.MethodPut, url, nil)
	if err != nil {
		t.Fatal(err)
	}
	//	req.Header.Set("User-Agent", "Test_nhcCmd")
	rsp, getErr := hCli.Do(req)
	if getErr != nil {
		fmt.Println(err)
	}
	got, readErr := ioutil.ReadAll(rsp.Body)
	if readErr != nil {
		fmt.Println("Read err: ", readErr)
	}
	//defer rsp.Body.Close()
	if string(got) != expected {
		t.Errorf("Test_nhcCmd failed, expecting %v, got %v", expected, string(got))
	}

} */
<|endoftext|>