<commit_before>/*

*/
package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"time"

	"github.com/dchest/blake2b"
	"github.com/gorilla/mux"
	"github.com/tsileo/blobstash/router"
	"github.com/tsileo/blobstash/vkv"
)

func WriteJSON(w http.ResponseWriter, data interface{}) {
	js, err := json.Marshal(data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}

func vkvHandler(wg sync.WaitGroup, db *vkv.DB, kvUpdate chan *vkv.KeyValue) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		switch r.Method {
		case "GET":
			iversion := -1
			version := r.URL.Query().Get("version")
			if version != "" {
				iver, err := strconv.Atoi(version)
				if err != nil {
					panic(err)
				}
				iversion = iver
			}
			res, err := db.Get(vars["key"], iversion)
			if err != nil {
				if err == vkv.ErrNotFound {
					http.Error(w, http.StatusText(404), 404)
					return
				}
				panic(err)
			}
			WriteJSON(w, res)
		case "HEAD":
			exists, err := db.Check(vars["key"])
			if err != nil {
				panic(err)
			}
			if exists {
				return
			}
			http.Error(w, http.StatusText(404), 404)
			return
		case "DELETE":
			k := vars["key"]
			sversion := r.URL.Query().Get("version")
			if sversion == "" {
				http.Error(w, "version missing", 500)
				return
			}
			version, err := strconv.Atoi(sversion)
			if err != nil {
				http.Error(w, "bad version", 500)
				return
			}
			hash, err := db.MetaBlob(k, version)
			if err != nil {
				panic(err)
			}
			log.Printf("%v", hash)
			// TODO delete blob
			if err := db.DeleteVersion(k, version); err != nil {
				panic(err)
			}
		case "PUT":
			wg.Add(1)
			defer wg.Done()
			k := vars["key"]
			hah, err := ioutil.ReadAll(r.Body)
			values, err := url.ParseQuery(string(hah))
			if err != nil {
				panic(err)
			}
			v := values.Get("value")
			sversion := values.Get("version")
			version := -1
			if sversion != "" {
				iversion, err := strconv.Atoi(sversion)
				if err != nil {
					http.Error(w, "bad version", 500)
					return
				}
				version = iversion
			}
			res, err := db.Put(k, v, version)
			if err != nil {
				panic(err)
			}
			kvUpdate <- res
			WriteJSON(w, res)
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func vkvVersionsHandler(db *vkv.DB) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			// TODO handle start/end/limit
			vars := mux.Vars(r)
			res, err := db.Versions(vars["key"], 0, int(time.Now().UTC().UnixNano()), 0)
			if err != nil {
				panic(err)
			}
			WriteJSON(w, res)
			return
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func vkvKeysHandler(db *vkv.DB) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			q := r.URL.Query()
			end := q.Get("end")
			if end == "" {
				end = "\xff"
			}
			limit := 0
			if q.Get("limit") != "" {
				ilimit, err := strconv.Atoi(q.Get("limit"))
				if err != nil {
					http.Error(w, "bad limit", 500)
				}
				limit = ilimit
			}
			res, err := db.Keys(q.Get("start"), end, limit)
			if err != nil {
				panic(err)
			}
			WriteJSON(w, map[string]interface{}{"keys": res})
			return
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func blobUploadHandler(blobs chan<- *router.Blob) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		//POST takes the uploaded file(s) and saves it to disk.
		case "POST":
			//parse the multipart form in the request
			mr, err := r.MultipartReader()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			for {
				part, err := mr.NextPart()
				if err == io.EOF {
					break
				}
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}
				hash := part.FormName()
				var buf bytes.Buffer
				buf.ReadFrom(part)
				blob := buf.Bytes()
				chash := fmt.Sprintf("%x", blake2b.Sum256(blob))
				if hash != chash {
					http.Error(w, "blob corrupted, hash does not match", http.StatusInternalServerError)
					return
				}
				req := &router.Request{
					Type: router.Write,
				}
				blobs <- &router.Blob{Hash: hash, Req: req, Blob: blob}
			}
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func blobHandler(blobrouter *router.Router) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		req := &router.Request{
			Type: router.Read,
		}
		backend := blobrouter.Route(req)
		switch r.Method {
		case "GET":
			blob, err := backend.Get(vars["hash"])
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			w.Write(blob)
			return
		case "HEAD":
			exists, err := backend.Exists(vars["hash"])
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			if exists {
				return
			}
			http.Error(w, http.StatusText(404), 404)
			return
		case "DELETE":
			//if err := backend.Delete(vars["hash"]); err != nil {
			//	panic(err)
			//}
			return
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func New(wg sync.WaitGroup, db *vkv.DB, kvUpdate chan *vkv.KeyValue, blobrouter *router.Router, blobs chan<- *router.Blob) *mux.Router {
	r := mux.NewRouter()
	r.HandleFunc("/api/v1/blobstore/upload", blobUploadHandler(blobs))
	r.HandleFunc("/api/v1/blobstore/blob/{hash}", blobHandler(blobrouter))
	r.HandleFunc("/api/v1/vkv/keys", vkvKeysHandler(db))
	r.HandleFunc("/api/v1/vkv/key/{key}", vkvHandler(wg, db, kvUpdate))
	r.HandleFunc("/api/v1/vkv/key/{key}/versions", vkvVersionsHandler(db))
	return r
}
<commit_msg>api: actually delete blob<commit_after>/*

*/
package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"sync"
	"time"

	"github.com/dchest/blake2b"
	"github.com/gorilla/mux"
	"github.com/tsileo/blobstash/router"
	"github.com/tsileo/blobstash/vkv"
)

func WriteJSON(w http.ResponseWriter, data interface{}) {
	js, err := json.Marshal(data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}

func vkvHandler(wg sync.WaitGroup, db *vkv.DB, kvUpdate chan *vkv.KeyValue) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		switch r.Method {
		case "GET":
			iversion := -1
			version := r.URL.Query().Get("version")
			if version != "" {
				iver, err := strconv.Atoi(version)
				if err != nil {
					panic(err)
				}
				iversion = iver
			}
			res, err := db.Get(vars["key"], iversion)
			if err != nil {
				if err == vkv.ErrNotFound {
					http.Error(w, http.StatusText(404), 404)
					return
				}
				panic(err)
			}
			WriteJSON(w, res)
		case "HEAD":
			exists, err := db.Check(vars["key"])
			if err != nil {
				panic(err)
			}
			if exists {
				return
			}
			http.Error(w, http.StatusText(404), 404)
			return
		case "DELETE":
			k := vars["key"]
			sversion := r.URL.Query().Get("version")
			if sversion == "" {
				http.Error(w, "version missing", 500)
				return
			}
			version, err := strconv.Atoi(sversion)
			if err != nil {
				http.Error(w, "bad version", 500)
				return
			}
			hash, err := db.MetaBlob(k, version)
			if err != nil {
				panic(err)
			}
			if err := db.DeleteVersion(k, version); err != nil {
				panic(err)
			}
			req := &router.Request{
				Type:     router.Read,
				MetaBlob: true,
			}
			if err := blobrouter.Route(req).Delete(hash); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
		case "PUT":
			wg.Add(1)
			defer wg.Done()
			k := vars["key"]
			hah, err := ioutil.ReadAll(r.Body)
			values, err := url.ParseQuery(string(hah))
			if err != nil {
				panic(err)
			}
			v := values.Get("value")
			sversion := values.Get("version")
			version := -1
			if sversion != "" {
				iversion, err := strconv.Atoi(sversion)
				if err != nil {
					http.Error(w, "bad version", 500)
					return
				}
				version = iversion
			}
			res, err := db.Put(k, v, version)
			if err != nil {
				panic(err)
			}
			kvUpdate <- res
			WriteJSON(w, res)
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func vkvVersionsHandler(db *vkv.DB) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			// TODO handle start/end/limit
			vars := mux.Vars(r)
			res, err := db.Versions(vars["key"], 0, int(time.Now().UTC().UnixNano()), 0)
			if err != nil {
				panic(err)
			}
			WriteJSON(w, res)
			return
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func vkvKeysHandler(db *vkv.DB) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			q := r.URL.Query()
			end := q.Get("end")
			if end == "" {
				end = "\xff"
			}
			limit := 0
			if q.Get("limit") != "" {
				ilimit, err := strconv.Atoi(q.Get("limit"))
				if err != nil {
					http.Error(w, "bad limit", 500)
				}
				limit = ilimit
			}
			res, err := db.Keys(q.Get("start"), end, limit)
			if err != nil {
				panic(err)
			}
			WriteJSON(w, map[string]interface{}{"keys": res})
			return
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func blobUploadHandler(blobs chan<- *router.Blob) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		//POST takes the uploaded file(s) and saves it to disk.
		case "POST":
			//parse the multipart form in the request
			mr, err := r.MultipartReader()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			for {
				part, err := mr.NextPart()
				if err == io.EOF {
					break
				}
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}
				hash := part.FormName()
				var buf bytes.Buffer
				buf.ReadFrom(part)
				blob := buf.Bytes()
				chash := fmt.Sprintf("%x", blake2b.Sum256(blob))
				if hash != chash {
					http.Error(w, "blob corrupted, hash does not match", http.StatusInternalServerError)
					return
				}
				req := &router.Request{
					Type: router.Write,
				}
				blobs <- &router.Blob{Hash: hash, Req: req, Blob: blob}
			}
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func blobHandler(blobrouter *router.Router) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		vars := mux.Vars(r)
		req := &router.Request{
			Type: router.Read,
		}
		backend := blobrouter.Route(req)
		switch r.Method {
		case "GET":
			blob, err := backend.Get(vars["hash"])
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			w.Write(blob)
			return
		case "HEAD":
			exists, err := backend.Exists(vars["hash"])
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			if exists {
				return
			}
			http.Error(w, http.StatusText(404), 404)
			return
		case "DELETE":
			if err := backend.Delete(vars["hash"]); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			return
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	}
}

func New(wg sync.WaitGroup, db *vkv.DB, kvUpdate chan *vkv.KeyValue, blobrouter *router.Router, blobs chan<- *router.Blob) *mux.Router {
	r := mux.NewRouter()
	r.HandleFunc("/api/v1/blobstore/upload", blobUploadHandler(blobs))
	r.HandleFunc("/api/v1/blobstore/blob/{hash}", blobHandler(blobrouter))
	r.HandleFunc("/api/v1/vkv/keys", vkvKeysHandler(db))
	r.HandleFunc("/api/v1/vkv/key/{key}", vkvHandler(wg, db, kvUpdate))
	r.HandleFunc("/api/v1/vkv/key/{key}/versions", vkvVersionsHandler(db))
	return r
}
<|endoftext|>


<commit_before>// Copyright 2015 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

// fbb provides a client-side implementation of the B2 Forwarding Protocol
// and Winlink 2000 Message Structure for transfer of messages to and from
// a Winlink 2000 Radio Email Server (RMS) gateway.
package fbb

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"strings"
	"time"

	"github.com/la5nta/wl2k-go/transport"
)

// Objects implementing the MBoxHandler interface can be used to handle inbound and outbound messages for a Session.
type MBoxHandler interface {
	InboundHandler
	OutboundHandler

	// Prepare is called before any other operation in a session.
	//
	// The returned error can be used to indicate that the mailbox is
	// not ready for a new session, the error will be forwarded to the
	// remote node.
	Prepare() error
}

// An OutboundHandler offer messages that can be delivered (a proposal) to the remote node and is notified when a message is sent or defered.
type OutboundHandler interface {
	// GetOutbound should return all pending (outbound) messages addressed to (and only to) one of the fw addresses.
	//
	// No fw address implies that the remote node could be a Winlink CMS and all oubound
	// messages can be delivered through the connected node.
	GetOutbound(fw ...Address) (out []*Message)

	// SetSent should mark the the message identified by MID as successfully sent.
	//
	// If rejected is true, it implies that the remote node has already received the message.
	SetSent(MID string, rejected bool)

	// SetDeferred should mark the outbound message identified by MID as deferred.
	//
	// SetDeferred is called when the remote want's to receive the proposed message
	// (see MID) later.
	SetDeferred(MID string)
}

// An InboundHandler handles all messages that can/is sent from the remote node.
type InboundHandler interface {
	// ProcessInbound should persist/save/process all messages received (msgs) returning an error if the operation was unsuccessful.
	//
	// The error will be delivered (if possble) to the remote to indicate that an error has occurred.
	ProcessInbound(msg ...*Message) error

	// GetInboundAnswer should return a ProposalAnwer (Accept/Reject/Defer) based on the remote's message Proposal p.
	//
	// An already successfully received message (see MID) should be rejected.
	GetInboundAnswer(p Proposal) ProposalAnswer
}

// Session represents a B2F exchange session.
//
// A session should only be used once.
type Session struct {
	mycall     string
	targetcall string
	locator    string
	motd       []string

	h             MBoxHandler
	statusUpdater StatusUpdater

	// Callback when secure login password is needed
	secureLoginHandleFunc func() (password string, err error)

	master     bool
	robustMode robustMode

	remoteSID sid
	remoteFW  []Address // Addresses the remote requests messages on behalf of
	localFW   []Address // Addresses we request messages on behalf of

	trafficStats TrafficStats

	quitReceived bool
	quitSent     bool
	remoteNoMsgs bool // True if last remote turn had no more messages

	rd *bufio.Reader

	log  *log.Logger
	pLog *log.Logger
	ua   UserAgent
}

// Struct used to hold information that is reported during B2F handshake.
//
// Non of the fields must contain a dash (-).
//
type UserAgent struct {
	Name    string
	Version string
}

type StatusUpdater interface {
	UpdateStatus(s Status)
}

// Status holds information about ongoing transfers.
type Status struct {
	Receiving        *Proposal
	Sending          *Proposal
	BytesTransferred int
	BytesTotal       int
	When             time.Time
}

// TrafficStats holds exchange message traffic statistics.
type TrafficStats struct {
	Received []string // Received message MIDs.
	Sent     []string // Sent message MIDs.
}

var StdLogger = log.New(os.Stderr, "", log.LstdFlags)
var StdUA = UserAgent{Name: "wl2kgo", Version: "0.1a"}

// Constructs a new Session object.
//
// The Handler can be nil (but no messages will be exchanged).
//
// Mycall and targetcall will be upper-cased.
func NewSession(mycall, targetcall, locator string, h MBoxHandler) *Session {
	mycall, targetcall = strings.ToUpper(mycall), strings.ToUpper(targetcall)

	return &Session{
		mycall:     mycall,
		localFW:    []Address{AddressFromString(mycall)},
		targetcall: targetcall,
		log:        StdLogger,
		h:          h,
		pLog:       StdLogger,
		ua:         StdUA,
		locator:    locator,
		trafficStats: TrafficStats{
			Received: make([]string, 0),
			Sent:     make([]string, 0),
		},
	}
}

type robustMode int

// The different robust-mode settings.
const (
	RobustAuto     robustMode = iota // Run the connection in robust-mode when not transferring outbound messages.
	RobustForced                     // Always run the connection in robust-mode.
	RobustDisabled                   // Never run the connection in robust-mode.
)

// SetRobustMode sets the RobustMode for this exchange.
//
// The mode is ignored if the exchange connection does not implement the transport.Robust interface.
//
// Default is RobustAuto.
func (s *Session) SetRobustMode(mode robustMode) {
	s.robustMode = mode
	//TODO: If NewSession took the net.Conn (not Exchange), we could return an error here to indicate that the operation was unsupported.
}

// SetMOTD sets one or more lines to be sent before handshake.
//
// The MOTD is only sent if the local node is session master.
func (s *Session) SetMOTD(line ...string) { s.motd = line }

// IsMaster sets whether this end should initiate the handshake.
func (s *Session) IsMaster(isMaster bool) { s.master = isMaster }

// RemoteSID returns the remote's SID (if available).
func (s *Session) RemoteSID() string { return string(s.remoteSID) }

// Exchange is the main method for exchanging messages with a remote over the B2F protocol.
//
// Sends outbound messages and downloads inbound messages prepared for this session.
//
// Outbound messages should be added as proposals before calling the Exchange() method.
//
// If conn implements the transport.Robust interface, the connection is run in robust-mode
// except when an outbound message is transferred.
//
// After Exchange(), messages that was accepted and delivered successfully to the RMS is
// available through a call to Sent(). Messages downloaded successfully from the RMS is
// retrieved by calling Received().
//
// The connection is closed at the end of the exchange. If the connection is closed before
// the exchange is done, is will return io.EOF.
//
// Subsequent Exchange calls on the same session is a noop.
func (s *Session) Exchange(conn net.Conn) (stats TrafficStats, err error) {
	if s.Done() {
		return stats, nil
	}

	// The given conn should always be closed after returning from this method.
	// If an error occured, echo it to the remote.
	defer func() {
		if err == nil {
			return
		}

		// In case another go-routine closes the connection...
		localEOF := strings.Contains(err.Error(), "use of closed network connection")
		if localEOF {
			err = io.EOF
		}

		if err != io.EOF {
			conn.SetDeadline(time.Now().Add(time.Minute))
			fmt.Fprintf(conn, "*** %s\r\n", err)
			conn.Close()
		} else {
			err = io.ErrUnexpectedEOF
		}
	}()

	// Prepare mailbox handler
	if s.h != nil {
		err = s.h.Prepare()
		if err != nil {
			return
		}
	}

	// Set connection's robust-mode according to setting
	if r, ok := conn.(transport.Robust); ok {
		r.SetRobust(s.robustMode != RobustDisabled)
		defer r.SetRobust(false)
	}

	s.rd = bufio.NewReader(conn)

	err = s.handshake(conn)
	if err != nil {
		return
	}

	if gzipExperimentEnabled() && s.remoteSID.Has(sGzip) {
		s.log.Println("GZIP_EXPERIMENT:", "Gzip compression enabled in this session.")
	}

	for myTurn := !s.master; !s.Done(); myTurn = !myTurn {
		if myTurn {
			s.quitSent, err = s.handleOutbound(conn)
		} else {
			s.quitReceived, err = s.handleInbound(conn)
		}

		if err != nil {
			return s.trafficStats, err
		}
	}

	return s.trafficStats, conn.Close()
}

// Done() returns true if either parties have existed from this session.
func (s *Session) Done() bool { return s.quitReceived || s.quitSent }

// Waits for connection to be closed, returning an error if seen on the line.
func waitRemoteHangup(conn net.Conn) error {
	conn.SetDeadline(time.Now().Add(time.Minute))

	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		line := scanner.Text()

		if err := errLine(line); err != nil {
			conn.Close()
			return err
		}
		log.Println(line)
	}
	return scanner.Err()
}

func remoteErr(str string) error {
	if !strings.HasPrefix(str, "***") {
		return nil
	}

	idx := strings.LastIndex(str, "*")
	if idx+1 >= len(str) {
		return nil
	}

	return fmt.Errorf(strings.TrimSpace(str[idx+1:]))
}

// Mycall returns this stations call sign.
func (s *Session) Mycall() string { return s.mycall }

// Targetcall returns the remote stations call sign (if known).
func (s *Session) Targetcall() string { return s.targetcall }

// SetSecureLoginHandleFunc registers a callback function used to prompt for password when a secure login challenge is received.
func (s *Session) SetSecureLoginHandleFunc(f func() (password string, err error)) {
	s.secureLoginHandleFunc = f
}

// This method returns the call signs the remote is requesting traffic on behalf of. The call signs are not available until
// the handshake is done.
//
// It will typically be the call sign of the remote P2P station and empty when the remote is a Winlink CMS.
func (s *Session) RemoteForwarders() []Address { return s.remoteFW }

// AddAuxiliaryAddress adds one or more addresses to request messages on behalf of.
//
// Currently the Winlink System only support requesting messages for call signs, not full email addresses.
func (s *Session) AddAuxiliaryAddress(aux ...Address) { s.localFW = append(s.localFW, aux...) }

// Set callback for status updates on receiving / sending messages
func (s *Session) SetStatusUpdater(updater StatusUpdater) { s.statusUpdater = updater }

// Sets custom logger.
func (s *Session) SetLogger(logger *log.Logger) {
	if logger == nil {
		logger = StdLogger
	}
	s.log = logger
	s.pLog = logger

}

// Set this session's user agent
func (s *Session) SetUserAgent(ua UserAgent) { s.ua = ua }

// Get this session's user agent
func (s *Session) UserAgent() UserAgent { return s.ua }

func (s *Session) outbound() []*Proposal {
	if s.h == nil {
		return []*Proposal{}
	}

	msgs := s.h.GetOutbound(s.remoteFW...)
	props := make([]*Proposal, 0, len(msgs))

	for _, m := range msgs {
		prop, err := m.Proposal(s.highestPropCode())
		if err != nil {
			// TODO: This should result in an error somewhere
			s.log.Printf("Unable to prepare proposal for '%s'. Corrupt message? Skipping...", prop.MID())
			continue
		}

		props = append(props, prop)
	}
	return props
}

func (s *Session) highestPropCode() PropCode {
	if s.remoteSID.Has(sGzip) && gzipExperimentEnabled() {
		return GzipProposal
	}
	return Wl2kProposal
}
<commit_msg>fbb: Send smallest messages first<commit_after>// Copyright 2016 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

// fbb provides a client-side implementation of the B2 Forwarding Protocol
// and Winlink 2000 Message Structure for transfer of messages to and from
// a Winlink 2000 Radio Email Server (RMS) gateway.
package fbb

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/la5nta/wl2k-go/transport"
)

// Objects implementing the MBoxHandler interface can be used to handle inbound and outbound messages for a Session.
type MBoxHandler interface {
	InboundHandler
	OutboundHandler

	// Prepare is called before any other operation in a session.
	//
	// The returned error can be used to indicate that the mailbox is
	// not ready for a new session, the error will be forwarded to the
	// remote node.
	Prepare() error
}

// An OutboundHandler offer messages that can be delivered (a proposal) to the remote node and is notified when a message is sent or defered.
type OutboundHandler interface {
	// GetOutbound should return all pending (outbound) messages addressed to (and only to) one of the fw addresses.
	//
	// No fw address implies that the remote node could be a Winlink CMS and all oubound
	// messages can be delivered through the connected node.
	GetOutbound(fw ...Address) (out []*Message)

	// SetSent should mark the the message identified by MID as successfully sent.
	//
	// If rejected is true, it implies that the remote node has already received the message.
	SetSent(MID string, rejected bool)

	// SetDeferred should mark the outbound message identified by MID as deferred.
	//
	// SetDeferred is called when the remote want's to receive the proposed message
	// (see MID) later.
	SetDeferred(MID string)
}

// An InboundHandler handles all messages that can/is sent from the remote node.
type InboundHandler interface {
	// ProcessInbound should persist/save/process all messages received (msgs) returning an error if the operation was unsuccessful.
	//
	// The error will be delivered (if possble) to the remote to indicate that an error has occurred.
	ProcessInbound(msg ...*Message) error

	// GetInboundAnswer should return a ProposalAnwer (Accept/Reject/Defer) based on the remote's message Proposal p.
	//
	// An already successfully received message (see MID) should be rejected.
	GetInboundAnswer(p Proposal) ProposalAnswer
}

// Session represents a B2F exchange session.
//
// A session should only be used once.
type Session struct {
	mycall     string
	targetcall string
	locator    string
	motd       []string

	h             MBoxHandler
	statusUpdater StatusUpdater

	// Callback when secure login password is needed
	secureLoginHandleFunc func() (password string, err error)

	master     bool
	robustMode robustMode

	remoteSID sid
	remoteFW  []Address // Addresses the remote requests messages on behalf of
	localFW   []Address // Addresses we request messages on behalf of

	trafficStats TrafficStats

	quitReceived bool
	quitSent     bool
	remoteNoMsgs bool // True if last remote turn had no more messages

	rd *bufio.Reader

	log  *log.Logger
	pLog *log.Logger
	ua   UserAgent
}

// Struct used to hold information that is reported during B2F handshake.
//
// Non of the fields must contain a dash (-).
//
type UserAgent struct {
	Name    string
	Version string
}

type StatusUpdater interface {
	UpdateStatus(s Status)
}

// Status holds information about ongoing transfers.
type Status struct {
	Receiving        *Proposal
	Sending          *Proposal
	BytesTransferred int
	BytesTotal       int
	When             time.Time
}

// TrafficStats holds exchange message traffic statistics.
type TrafficStats struct {
	Received []string // Received message MIDs.
	Sent     []string // Sent message MIDs.
}

var StdLogger = log.New(os.Stderr, "", log.LstdFlags)
var StdUA = UserAgent{Name: "wl2kgo", Version: "0.1a"}

// Constructs a new Session object.
//
// The Handler can be nil (but no messages will be exchanged).
//
// Mycall and targetcall will be upper-cased.
func NewSession(mycall, targetcall, locator string, h MBoxHandler) *Session {
	mycall, targetcall = strings.ToUpper(mycall), strings.ToUpper(targetcall)

	return &Session{
		mycall:     mycall,
		localFW:    []Address{AddressFromString(mycall)},
		targetcall: targetcall,
		log:        StdLogger,
		h:          h,
		pLog:       StdLogger,
		ua:         StdUA,
		locator:    locator,
		trafficStats: TrafficStats{
			Received: make([]string, 0),
			Sent:     make([]string, 0),
		},
	}
}

type robustMode int

// The different robust-mode settings.
const (
	RobustAuto     robustMode = iota // Run the connection in robust-mode when not transferring outbound messages.
	RobustForced                     // Always run the connection in robust-mode.
	RobustDisabled                   // Never run the connection in robust-mode.
)

// SetRobustMode sets the RobustMode for this exchange.
//
// The mode is ignored if the exchange connection does not implement the transport.Robust interface.
//
// Default is RobustAuto.
func (s *Session) SetRobustMode(mode robustMode) {
	s.robustMode = mode
	//TODO: If NewSession took the net.Conn (not Exchange), we could return an error here to indicate that the operation was unsupported.
}

// SetMOTD sets one or more lines to be sent before handshake.
//
// The MOTD is only sent if the local node is session master.
func (s *Session) SetMOTD(line ...string) { s.motd = line }

// IsMaster sets whether this end should initiate the handshake.
func (s *Session) IsMaster(isMaster bool) { s.master = isMaster }

// RemoteSID returns the remote's SID (if available).
func (s *Session) RemoteSID() string { return string(s.remoteSID) }

// Exchange is the main method for exchanging messages with a remote over the B2F protocol.
//
// Sends outbound messages and downloads inbound messages prepared for this session.
//
// Outbound messages should be added as proposals before calling the Exchange() method.
//
// If conn implements the transport.Robust interface, the connection is run in robust-mode
// except when an outbound message is transferred.
//
// After Exchange(), messages that was accepted and delivered successfully to the RMS is
// available through a call to Sent(). Messages downloaded successfully from the RMS is
// retrieved by calling Received().
//
// The connection is closed at the end of the exchange. If the connection is closed before
// the exchange is done, is will return io.EOF.
//
// Subsequent Exchange calls on the same session is a noop.
func (s *Session) Exchange(conn net.Conn) (stats TrafficStats, err error) {
	if s.Done() {
		return stats, nil
	}

	// The given conn should always be closed after returning from this method.
	// If an error occured, echo it to the remote.
	defer func() {
		if err == nil {
			return
		}

		// In case another go-routine closes the connection...
		localEOF := strings.Contains(err.Error(), "use of closed network connection")
		if localEOF {
			err = io.EOF
		}

		if err != io.EOF {
			conn.SetDeadline(time.Now().Add(time.Minute))
			fmt.Fprintf(conn, "*** %s\r\n", err)
			conn.Close()
		} else {
			err = io.ErrUnexpectedEOF
		}
	}()

	// Prepare mailbox handler
	if s.h != nil {
		err = s.h.Prepare()
		if err != nil {
			return
		}
	}

	// Set connection's robust-mode according to setting
	if r, ok := conn.(transport.Robust); ok {
		r.SetRobust(s.robustMode != RobustDisabled)
		defer r.SetRobust(false)
	}

	s.rd = bufio.NewReader(conn)

	err = s.handshake(conn)
	if err != nil {
		return
	}

	if gzipExperimentEnabled() && s.remoteSID.Has(sGzip) {
		s.log.Println("GZIP_EXPERIMENT:", "Gzip compression enabled in this session.")
	}

	for myTurn := !s.master; !s.Done(); myTurn = !myTurn {
		if myTurn {
			s.quitSent, err = s.handleOutbound(conn)
		} else {
			s.quitReceived, err = s.handleInbound(conn)
		}

		if err != nil {
			return s.trafficStats, err
		}
	}

	return s.trafficStats, conn.Close()
}

// Done() returns true if either parties have existed from this session.
func (s *Session) Done() bool { return s.quitReceived || s.quitSent }

// Waits for connection to be closed, returning an error if seen on the line.
func waitRemoteHangup(conn net.Conn) error {
	conn.SetDeadline(time.Now().Add(time.Minute))

	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		line := scanner.Text()

		if err := errLine(line); err != nil {
			conn.Close()
			return err
		}
		log.Println(line)
	}
	return scanner.Err()
}

func remoteErr(str string) error {
	if !strings.HasPrefix(str, "***") {
		return nil
	}

	idx := strings.LastIndex(str, "*")
	if idx+1 >= len(str) {
		return nil
	}

	return fmt.Errorf(strings.TrimSpace(str[idx+1:]))
}

// Mycall returns this stations call sign.
func (s *Session) Mycall() string { return s.mycall }

// Targetcall returns the remote stations call sign (if known).
func (s *Session) Targetcall() string { return s.targetcall }

// SetSecureLoginHandleFunc registers a callback function used to prompt for password when a secure login challenge is received.
func (s *Session) SetSecureLoginHandleFunc(f func() (password string, err error)) {
	s.secureLoginHandleFunc = f
}

// This method returns the call signs the remote is requesting traffic on behalf of. The call signs are not available until
// the handshake is done.
//
// It will typically be the call sign of the remote P2P station and empty when the remote is a Winlink CMS.
func (s *Session) RemoteForwarders() []Address { return s.remoteFW }

// AddAuxiliaryAddress adds one or more addresses to request messages on behalf of.
//
// Currently the Winlink System only support requesting messages for call signs, not full email addresses.
func (s *Session) AddAuxiliaryAddress(aux ...Address) { s.localFW = append(s.localFW, aux...) }

// Set callback for status updates on receiving / sending messages
func (s *Session) SetStatusUpdater(updater StatusUpdater) { s.statusUpdater = updater }

// Sets custom logger.
func (s *Session) SetLogger(logger *log.Logger) {
	if logger == nil {
		logger = StdLogger
	}
	s.log = logger
	s.pLog = logger

}

// Set this session's user agent
func (s *Session) SetUserAgent(ua UserAgent) { s.ua = ua }

// Get this session's user agent
func (s *Session) UserAgent() UserAgent { return s.ua }

func (s *Session) outbound() []*Proposal {
	if s.h == nil {
		return []*Proposal{}
	}

	msgs := s.h.GetOutbound(s.remoteFW...)
	props := make([]*Proposal, 0, len(msgs))

	for _, m := range msgs {
		prop, err := m.Proposal(s.highestPropCode())
		if err != nil {
			// TODO: This should result in an error somewhere
			s.log.Printf("Unable to prepare proposal for '%s'. Corrupt message? Skipping...", prop.MID())
			continue
		}

		props = append(props, prop)
	}

	// Sort the proposals by size, smallest first as suggested by the Winlink FAQ Q460.
	sort.Sort(bySize(props))

	return props
}

type bySize []*Proposal

func (s bySize) Len() int      { return len(s) }
func (s bySize) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s bySize) Less(i, j int) bool {
	if s[i].compressedSize != s[j].compressedSize {
		return s[i].compressedSize < s[j].compressedSize
	}
	return s[i].MID() < s[j].MID()
}

func (s *Session) highestPropCode() PropCode {
	if s.remoteSID.Has(sGzip) && gzipExperimentEnabled() {
		return GzipProposal
	}
	return Wl2kProposal
}
<|endoftext|>


<commit_before>// Copyright (C) Pagoda Box, Inc - All Rights Reserved
// Unauthorized copying of this file, via any medium is strictly prohibited
// Proprietary and confidential

// Package api provides a restful interface to view aggregated stats as well as manage alerts.
package api

//
import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"

	"github.com/gorilla/pat"
	"github.com/jcelliott/lumber"
	"github.com/nanobox-io/golang-nanoauth"
	"github.com/spf13/viper"
)

// structs
type (
	apiError struct {
		ErrorString string `json:"error"`
	}
	apiMsg struct {
		MsgString string `json:"msg"`
	}
)

var (
	BadJson      = errors.New("Bad JSON syntax received in body")
	BodyReadFail = errors.New("Body Read Failed")
)

// start sets the state of the package if the config has all the necessary data for the api
// and starts the default api server; routing web requests and handling all the routes
func Start() error {
	routes, err := registerRoutes()
	if err != nil {
		return err
	}

	nanoauth.DefaultAuth.Header = "X-AUTH-TOKEN"

	// blocking...
	if viper.GetBool("insecure") {
		lumber.Info("[PULSE :: API] Listening at 'http://%s'...\n", viper.GetString("http-listen-address"))
		return nanoauth.ListenAndServe(viper.GetString("http-listen-address"), viper.GetString("token"), routes)
	}
	lumber.Info("[PULSE :: API] Listening at 'https://%s'...\n", viper.GetString("http-listen-address"))
	return nanoauth.ListenAndServeTLS(viper.GetString("http-listen-address"), viper.GetString("token"), routes)
}

// registerRoutes
func registerRoutes() (*pat.Router, error) {
	lumber.Debug("[PULSE :: API] Registering routes...")

	//
	router := pat.New()

	//
	router.Get("/ping", func(rw http.ResponseWriter, req *http.Request) {
		rw.Write([]byte("pong"))
	})

	router.Options("/", cors)

	router.Get("/keys", keysRequest)
	router.Get("/tags", tagsRequest)

	router.Get("/latest/{stat}", doCors(latestStat))
	router.Get("/hourly/{stat}", doCors(hourlyStat))
	router.Get("/daily/{stat}", doCors(dailyStat))
	router.Get("/daily_peaks/{stat}", doCors(dailyStat))

	// only expose alert routes if alerting configured
	if viper.GetString("kapacitor-address") != "" {
		// todo: maybe get and list tasks from kapacitor
		router.Post("/alerts", doCors(setAlert))
		router.Put("/alerts", doCors(setAlert))
		router.Delete("/alerts/{id}", doCors(deleteAlert))
	}

	return router, nil
}

func doCors(fn http.HandlerFunc) http.HandlerFunc {
	return func(rw http.ResponseWriter, req *http.Request) {
		rw.Header().Set("Access-Control-Allow-Origin", viper.GetString("cors-allow"))
		rw.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
		rw.Header().Set("Access-Control-Allow-Headers", "X-AUTH-TOKEN")

		fn(rw, req)
	}
}

func cors(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Access-Control-Allow-Origin", viper.GetString("cors-allow"))
	rw.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
	rw.Header().Set("Access-Control-Allow-Headers", "X-AUTH-TOKEN")
	writeBody(apiMsg{"Success"}, rw, http.StatusOK, req)
	return
}

// writeBody
func writeBody(v interface{}, rw http.ResponseWriter, status int, req *http.Request) error {
	b, err := json.Marshal(v)
	if err != nil {
		return err
	}

	// print the error only if there is one
	var msg map[string]string
	json.Unmarshal(b, &msg)

	var errMsg string
	if msg["error"] != "" {
		errMsg = msg["error"]
	}

	lumber.Debug(`[PULSE :: ACCESS] %v - [%v] %v %v %v - "User-Agent: %s" %s`,
		req.RemoteAddr, req.Proto, req.Method, req.RequestURI,
		status, req.Header.Get("User-Agent"), errMsg)

	rw.Header().Set("Content-Type", "application/json")
	rw.WriteHeader(status)
	rw.Write(append(b, byte('\n')))

	return nil
}

// parseBody parses the json body into v
func parseBody(req *http.Request, v interface{}) error {

	// read the body
	b, err := ioutil.ReadAll(req.Body)
	if err != nil {
		lumber.Error(err.Error())
		return BodyReadFail
	}
	defer req.Body.Close()

	// parse body and store in v
	err = json.Unmarshal(b, v)
	if err != nil {
		lumber.Error(err.Error())
		return BadJson
	}

	return nil
}
<commit_msg>Add x-csrf-token to allow-headers<commit_after>// Copyright (C) Pagoda Box, Inc - All Rights Reserved
// Unauthorized copying of this file, via any medium is strictly prohibited
// Proprietary and confidential

// Package api provides a restful interface to view aggregated stats as well as manage alerts.
package api

//
import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"

	"github.com/gorilla/pat"
	"github.com/jcelliott/lumber"
	"github.com/nanobox-io/golang-nanoauth"
	"github.com/spf13/viper"
)

// structs
type (
	apiError struct {
		ErrorString string `json:"error"`
	}
	apiMsg struct {
		MsgString string `json:"msg"`
	}
)

var (
	BadJson      = errors.New("Bad JSON syntax received in body")
	BodyReadFail = errors.New("Body Read Failed")
)

// start sets the state of the package if the config has all the necessary data for the api
// and starts the default api server; routing web requests and handling all the routes
func Start() error {
	routes, err := registerRoutes()
	if err != nil {
		return err
	}

	nanoauth.DefaultAuth.Header = "X-AUTH-TOKEN"

	// blocking...
	if viper.GetBool("insecure") {
		lumber.Info("[PULSE :: API] Listening at 'http://%s'...\n", viper.GetString("http-listen-address"))
		return nanoauth.ListenAndServe(viper.GetString("http-listen-address"), viper.GetString("token"), routes)
	}
	lumber.Info("[PULSE :: API] Listening at 'https://%s'...\n", viper.GetString("http-listen-address"))
	return nanoauth.ListenAndServeTLS(viper.GetString("http-listen-address"), viper.GetString("token"), routes)
}

// registerRoutes
func registerRoutes() (*pat.Router, error) {
	lumber.Debug("[PULSE :: API] Registering routes...")

	//
	router := pat.New()

	//
	router.Get("/ping", func(rw http.ResponseWriter, req *http.Request) {
		rw.Write([]byte("pong"))
	})

	router.Options("/", cors)

	router.Get("/keys", keysRequest)
	router.Get("/tags", tagsRequest)

	router.Get("/latest/{stat}", doCors(latestStat))
	router.Get("/hourly/{stat}", doCors(hourlyStat))
	router.Get("/daily/{stat}", doCors(dailyStat))
	router.Get("/daily_peaks/{stat}", doCors(dailyStat))

	// only expose alert routes if alerting configured
	if viper.GetString("kapacitor-address") != "" {
		// todo: maybe get and list tasks from kapacitor
		router.Post("/alerts", doCors(setAlert))
		router.Put("/alerts", doCors(setAlert))
		router.Delete("/alerts/{id}", doCors(deleteAlert))
	}

	return router, nil
}

func doCors(fn http.HandlerFunc) http.HandlerFunc {
	return func(rw http.ResponseWriter, req *http.Request) {
		rw.Header().Set("Access-Control-Allow-Origin", viper.GetString("cors-allow"))
		rw.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
		rw.Header().Set("Access-Control-Allow-Headers", "X-AUTH-TOKEN, X-CSRF-Token")

		fn(rw, req)
	}
}

func cors(rw http.ResponseWriter, req *http.Request) {
	rw.Header().Set("Access-Control-Allow-Origin", viper.GetString("cors-allow"))
	rw.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
	rw.Header().Set("Access-Control-Allow-Headers", "X-AUTH-TOKEN, X-CSRF-Token")
	writeBody(apiMsg{"Success"}, rw, http.StatusOK, req)
	return
}

// writeBody
func writeBody(v interface{}, rw http.ResponseWriter, status int, req *http.Request) error {
	b, err := json.Marshal(v)
	if err != nil {
		return err
	}

	// print the error only if there is one
	var msg map[string]string
	json.Unmarshal(b, &msg)

	var errMsg string
	if msg["error"] != "" {
		errMsg = msg["error"]
	}

	lumber.Debug(`[PULSE :: ACCESS] %v - [%v] %v %v %v - "User-Agent: %s" %s`,
		req.RemoteAddr, req.Proto, req.Method, req.RequestURI,
		status, req.Header.Get("User-Agent"), errMsg)

	rw.Header().Set("Content-Type", "application/json")
	rw.WriteHeader(status)
	rw.Write(append(b, byte('\n')))

	return nil
}

// parseBody parses the json body into v
func parseBody(req *http.Request, v interface{}) error {

	// read the body
	b, err := ioutil.ReadAll(req.Body)
	if err != nil {
		lumber.Error(err.Error())
		return BodyReadFail
	}
	defer req.Body.Close()

	// parse body and store in v
	err = json.Unmarshal(b, v)
	if err != nil {
		lumber.Error(err.Error())
		return BadJson
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import "fmt"

func main() {
    fmt.Printf("Hello, Jupitor\n")
}
<commit_msg>Added library support for reverse strings.<commit_after>package main

import (
	"fmt"
	"github/cloudseeder/stringutil"
)

func main() {
	fmt.Printf(stringutil.Reverse("!oG olleH"))
        fmt.Printf("And, Hello Jupiter\n")
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"

	"bytes"

	"github.com/davidlu1997/fast-shortener/config"
	"github.com/davidlu1997/fast-shortener/model"
	"github.com/davidlu1997/fast-shortener/shortener"
	"github.com/valyala/fasthttp"
)

type API struct {
	shortener shortener.Shortener
	config    *config.Configuration
}

func InitAPI(config *config.Configuration) *API {
	return &API{
		shortener: shortener.InitCacheShortener(config),
		config:    config,
	}
}

func (a *API) putLinkHandler(ctx *fasthttp.RequestCtx) {
	var link *model.Link
	if err := json.Unmarshal(ctx.PostBody(), &link); err != nil {
		ctx.Error(err.Error(), fasthttp.StatusBadRequest)
	}

	if !link.IsValid(a.config) {
		ctx.Error("", fasthttp.StatusBadRequest)
		return
	}

	if err := a.shortener.Put(link); err != nil {
		ctx.Error(err.Error(), fasthttp.StatusBadRequest)
		return
	}

	ctx.SetStatusCode(fasthttp.StatusOK)
}

func (a *API) getLinkHandler(ctx *fasthttp.RequestCtx) {
	path := ctx.Path()
	key := path[bytes.LastIndexByte(path, '/')+1:]

	link := a.shortener.Get(string(key))
	if link == nil {
		ctx.Error("", fasthttp.StatusNotFound)
	}

	json, err := json.Marshal(link)
	if err != nil {
		ctx.Error(err.Error(), fasthttp.StatusBadGateway)
		return
	}

	ctx.SetBody(json)
	ctx.SetStatusCode(fasthttp.StatusOK)
}

func (a *API) okHandler(ctx *fasthttp.RequestCtx) {
	ctx.SetStatusCode(fasthttp.StatusOK)
}

func (a *API) RequestHandler(ctx *fasthttp.RequestCtx) {
	switch string(ctx.Path()) {
	case "/put":
		a.putLinkHandler(ctx)
	case "/ok":
		a.okHandler(ctx)
	default:
		a.getLinkHandler(ctx)
	}
}
<commit_msg>Return properly on error<commit_after>package api

import (
	"encoding/json"

	"bytes"

	"github.com/davidlu1997/fast-shortener/config"
	"github.com/davidlu1997/fast-shortener/model"
	"github.com/davidlu1997/fast-shortener/shortener"
	"github.com/valyala/fasthttp"
)

type API struct {
	shortener shortener.Shortener
	config    *config.Configuration
}

func InitAPI(config *config.Configuration) *API {
	return &API{
		shortener: shortener.InitCacheShortener(config),
		config:    config,
	}
}

func (a *API) putLinkHandler(ctx *fasthttp.RequestCtx) {
	var link *model.Link
	if err := json.Unmarshal(ctx.PostBody(), &link); err != nil {
		ctx.Error(err.Error(), fasthttp.StatusBadRequest)
	}

	if !link.IsValid(a.config) {
		ctx.Error("", fasthttp.StatusBadRequest)
		return
	}

	if err := a.shortener.Put(link); err != nil {
		ctx.Error(err.Error(), fasthttp.StatusBadRequest)
		return
	}

	ctx.SetStatusCode(fasthttp.StatusOK)
}

func (a *API) getLinkHandler(ctx *fasthttp.RequestCtx) {
	path := ctx.Path()
	key := path[bytes.LastIndexByte(path, '/')+1:]
	link := a.shortener.Get(string(key))
	if link == nil {
		ctx.Error("", fasthttp.StatusNotFound)
		return
	}

	json, err := json.Marshal(link)
	if err != nil {
		ctx.Error(err.Error(), fasthttp.StatusBadGateway)
		return
	}

	ctx.SetBody(json)
	ctx.SetStatusCode(fasthttp.StatusOK)
}

func (a *API) okHandler(ctx *fasthttp.RequestCtx) {
	ctx.SetStatusCode(fasthttp.StatusOK)
}

func (a *API) RequestHandler(ctx *fasthttp.RequestCtx) {
	switch string(ctx.Path()) {
	case "/put":
		a.putLinkHandler(ctx)
	case "/ok":
		a.okHandler(ctx)
	default:
		a.getLinkHandler(ctx)
	}
}
<|endoftext|>


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

// Package xdgbasedir implements a freedesktop.org XDG Base Directory Specification.
// XDG Base Directory Specification:
//  https://specifications.freedesktop.org/basedir-spec/0.8/
//
// The XDG Base Directory Specification is based on the following concepts:
//
// There is a single base directory relative to which user-specific data files should be written. This directory is defined by the environment variable $XDG_DATA_HOME.
//
// There is a single base directory relative to which user-specific configuration files should be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
//
// There is a set of preference ordered base directories relative to which data files should be searched. This set of directories is defined by the environment variable $XDG_DATA_DIRS.
//
// There is a set of preference ordered base directories relative to which configuration files should be searched. This set of directories is defined by the environment variable $XDG_CONFIG_DIRS.
//
// There is a single base directory relative to which user-specific non-essential (cached) data should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
package xdgbasedir

import (
	"fmt"
	"os"
)

type mode int

const (
	// Unix unix mode directory structure.
	Unix mode = iota
	// Native native mode directory structure.
	Native
)

func (m mode) String() string {
	switch m {
	case Native:
		return "Native"
	case Unix:
		return "Unix"
	default:
		return fmt.Sprintf("mode (%d)", m)
	}
}

// Mode mode of directory structure. This config only available darwin.
var Mode mode

// DataHome return the XDG_DATA_HOME based directory path.
//
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
func DataHome() string {
	if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" {
		return dataHome
	}
	return dataHome()
}

// ConfigHome return the XDG_CONFIG_HOME based directory path.
//
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
func ConfigHome() string {
	if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
		return configHome
	}
	return configHome()
}

// DataDirs return the XDG_DATA_DIRS based directory path.
//
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition
// to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
// TODO(zchee): XDG_DATA_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func DataDirs() string {
	if dataDirs := os.Getenv("XDG_DATA_DIRS"); dataDirs != "" {
		return dataDirs
	}
	return dataDirs()
}

// ConfigDirs return the XDG_CONFIG_DIRS based directory path.
//
// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition
// to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be seperated with a colon ':'.
// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
// TODO(zchee): XDG_CONFIG_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func ConfigDirs() string {
	if configDirs := os.Getenv("XDG_CONFIG_DIRS"); configDirs != "" {
		return configDirs
	}
	return configDirs()
}

// CacheHome return the XDG_CACHE_HOME based directory path.
//
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
//
// TODO(zchee): In macOS, Is it better to use the ~/Library/Caches directory? Or add the configurable by users setting?
// Apple's "File System Programming Guide" describe the this directory should be used if users cache files.
// However, some user who is using the macOS as Unix-like prefers $HOME/.cache.
// xref:
//  https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html
func CacheHome() string {
	if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
		return cacheHome
	}
	return cacheHome()
}

// RuntimeDir return the XDG_RUNTIME_DIR based directory path.
//
// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and
// other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user,
// and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
//
// TODO(zchee): XDG_RUNTIME_DIR seems to change depending on the each distro or init system such as systemd.
// Also In macOS, normal user haven't permission for write to this directory.
// xref:
//	http://serverfault.com/questions/388840/good-default-for-xdg-runtime-dir/727994#727994
func RuntimeDir() string {
	if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
		return runtimeDir
	}
	return runtimeDir()
}
<commit_msg>xdgbasedir: rename local variable to d<commit_after>// Copyright 2017 The go-xdgbasedir Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package xdgbasedir implements a freedesktop.org XDG Base Directory Specification.
// XDG Base Directory Specification:
//  https://specifications.freedesktop.org/basedir-spec/0.8/
//
// The XDG Base Directory Specification is based on the following concepts:
//
// There is a single base directory relative to which user-specific data files should be written. This directory is defined by the environment variable $XDG_DATA_HOME.
//
// There is a single base directory relative to which user-specific configuration files should be written. This directory is defined by the environment variable $XDG_CONFIG_HOME.
//
// There is a set of preference ordered base directories relative to which data files should be searched. This set of directories is defined by the environment variable $XDG_DATA_DIRS.
//
// There is a set of preference ordered base directories relative to which configuration files should be searched. This set of directories is defined by the environment variable $XDG_CONFIG_DIRS.
//
// There is a single base directory relative to which user-specific non-essential (cached) data should be written. This directory is defined by the environment variable $XDG_CACHE_HOME.
package xdgbasedir

import (
	"fmt"
	"os"
)

type mode int

const (
	// Unix unix mode directory structure.
	Unix mode = iota
	// Native native mode directory structure.
	Native
)

func (m mode) String() string {
	switch m {
	case Native:
		return "Native"
	case Unix:
		return "Unix"
	default:
		return fmt.Sprintf("mode (%d)", m)
	}
}

// Mode mode of directory structure. This config only available darwin.
var Mode mode

// DataHome return the XDG_DATA_HOME based directory path.
//
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
func DataHome() string {
	if env := os.Getenv("XDG_DATA_HOME"); env != "" {
		return env
	}
	return dataHome()
}

// ConfigHome return the XDG_CONFIG_HOME based directory path.
//
// $XDG_CONFIG_HOME defines the base directory relative to which user specific configuration files should be stored.
// If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.
func ConfigHome() string {
	if env := os.Getenv("XDG_CONFIG_HOME"); env != "" {
		return env
	}
	return configHome()
}

// DataDirs return the XDG_DATA_DIRS based directory path.
//
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition
// to the $XDG_DATA_HOME base directory. The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
// TODO(zchee): XDG_DATA_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func DataDirs() string {
	if env := os.Getenv("XDG_DATA_DIRS"); env != "" {
		return env
	}
	return dataDirs()
}

// ConfigDirs return the XDG_CONFIG_DIRS based directory path.
//
// $XDG_CONFIG_DIRS defines the preference-ordered set of base directories to search for configuration files in addition
// to the $XDG_CONFIG_HOME base directory. The directories in $XDG_CONFIG_DIRS should be seperated with a colon ':'.
// If $XDG_CONFIG_DIRS is either not set or empty, a value equal to /etc/xdg should be used.
// TODO(zchee): XDG_CONFIG_DIRS should be seperated with a colon, We should change return type to the []string
// which colon seperated value instead of string?
func ConfigDirs() string {
	if env := os.Getenv("XDG_CONFIG_DIRS"); env != "" {
		return env
	}
	return configDirs()
}

// CacheHome return the XDG_CACHE_HOME based directory path.
//
// $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored.
// If $XDG_CACHE_HOME is either not set or empty, a default equal to $HOME/.cache should be used.
//
// TODO(zchee): In macOS, Is it better to use the ~/Library/Caches directory? Or add the configurable by users setting?
// Apple's "File System Programming Guide" describe the this directory should be used if users cache files.
// However, some user who is using the macOS as Unix-like prefers $HOME/.cache.
// xref:
//  https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html
func CacheHome() string {
	if env := os.Getenv("XDG_CACHE_HOME"); env != "" {
		return env
	}
	return cacheHome()
}

// RuntimeDir return the XDG_RUNTIME_DIR based directory path.
//
// $XDG_RUNTIME_DIR defines the base directory relative to which user-specific non-essential runtime files and
// other file objects (such as sockets, named pipes, ...) should be stored. The directory MUST be owned by the user,
// and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700.
//
// TODO(zchee): XDG_RUNTIME_DIR seems to change depending on the each distro or init system such as systemd.
// Also In macOS, normal user haven't permission for write to this directory.
// xref:
//	http://serverfault.com/questions/388840/good-default-for-xdg-runtime-dir/727994#727994
func RuntimeDir() string {
	if env := os.Getenv("XDG_RUNTIME_DIR"); env != "" {
		return env
	}
	return runtimeDir()
}
<|endoftext|>


<commit_before>package blas

// Usdot (Sparse dot product (r <- x^T * y)) calculates the dot product of
// sparse vector x and dense vector y.  indx is used as the index
// values to gather and incy as the stride for y.
func Usdot(x []float64, indx []int, y []float64, incy int) float64 {
	var dot float64
	for i, index := range indx {
		dot += x[i] * y[index*incy]
	}
	return dot
}

// Usaxpy (Sparse update (y <- a * x + y)) scales the sparse vector x by
// alpha and adds the result to the dense vector y.  indx is used as the index
// values to gather and incy as the stride for y.
func Usaxpy(alpha float64, x []float64, indx []int, y []float64, incy int) {
	if alpha == 0 {
		return
	}
	for i, index := range indx {
		y[index*incy] += alpha * x[i]
	}
}

// Usga (Sparse gather (x <- y|x)) gathers entries from the dense vector
// y into the sparse vector x using indx as the index values to gather
// and incy as the stride for y.
func Usga(y []float64, incy int, x []float64, indx []int) {
	for i, index := range indx {
		x[i] = y[index*incy]
	}
}

// Usgz (Sparse gather and zero (x <- y|x, y|x <- 0)) gathers entries
// from the dense vector y into the sparse vector x
// (as Usga()) and then sets the corresponding elements of y (y[indx[i]])
// to 0.  indx is used as the index values to gather and incy as the stride
// for y.
func Usgz(y []float64, incy int, x []float64, indx []int) {
	for i, index := range indx {
		x[i] = y[index*incy]
		y[index*incy] = 0
	}
}

// Ussc (Sparse scatter (y|x <- x)) scatters enries into the dense vector y
// from the sparse vector x using indx as the index values to scatter to
// and incy as the stride for y. The function will panic if x and idx are
// different lengths.
func Ussc(x []float64, y []float64, incy int, indx []int) {
	for i, index := range indx {
		y[index*incy] = x[i]
	}
}
<commit_msg>updated documentation<commit_after>package blas

// Usdot (Sparse dot product (r <- x^T * y)) calculates the dot product of
// sparse vector x and dense vector y.  indx is used as the index
// values to gather and incy as the stride for y.
func Usdot(x []float64, indx []int, y []float64, incy int) float64 {
	var dot float64
	for i, index := range indx {
		dot += x[i] * y[index*incy]
	}
	return dot
}

// Usaxpy (Sparse update (y <- alpha * x + y)) scales the sparse vector x by
// alpha and adds the result to the dense vector y.  indx is used as the index
// values to gather and incy as the stride for y.
func Usaxpy(alpha float64, x []float64, indx []int, y []float64, incy int) {
	if alpha == 0 {
		return
	}
	for i, index := range indx {
		y[index*incy] += alpha * x[i]
	}
}

// Usga (Sparse gather (x <- y|x)) gathers entries from the dense vector
// y into the sparse vector x using indx as the index values to gather
// and incy as the stride for y.
func Usga(y []float64, incy int, x []float64, indx []int) {
	for i, index := range indx {
		x[i] = y[index*incy]
	}
}

// Usgz (Sparse gather and zero (x <- y|x, y|x <- 0)) gathers entries
// from the dense vector y into the sparse vector x
// (as Usga()) and then sets the corresponding elements of y (y[indx[i]])
// to 0.  indx is used as the index values to gather and incy as the stride
// for y.
func Usgz(y []float64, incy int, x []float64, indx []int) {
	for i, index := range indx {
		x[i] = y[index*incy]
		y[index*incy] = 0
	}
}

// Ussc (Sparse scatter (y|x <- x)) scatters enries into the dense vector y
// from the sparse vector x using indx as the index values to scatter to
// and incy as the stride for y. The function will panic if x and idx are
// different lengths.
func Ussc(x []float64, y []float64, incy int, indx []int) {
	for i, index := range indx {
		y[index*incy] = x[i]
	}
}
<|endoftext|>


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

// Documentation from http://www.ncbi.nlm.nih.gov/books/NBK21097/

// Package blast provides support for interaction with the NCBI BLAST service.
//
// Please see http://blast.ncbi.nlm.nih.gov/Blast.cgi?CMD=Web&PAGE_TYPE=BlastDocs&DOC_TYPE=DeveloperInfo
// for the BLAST service usage policy.
//
// Required parameters are specified by name in the function call. Optional parameters are
// passed via parameter struct values. See the 'QBlast URL API User's Guide' at
// http://www.ncbi.nlm.nih.gov/BLAST/Doc/urlapi.html for explanation of the use of these
// programs.
//
// The following two parameters should be included in all BLAST requests.
//
//  tool   Name of application making the BLAST call. Its value must be a string with no
//         internal spaces.
//
//  email  E-mail address of the BLAST user. Its value must be a string with no internal
//         spaces, and should be a valid e-mail address.
package blast

import (
	"code.google.com/p/biogo.ncbi"

	"errors"
	"fmt"
	"io"
	"net/url"
	"reflect"
	"strings"
	"time"
)

var (
	ErrNoRidProvided = errors.New("blast: no RID provided")
	ErrMissingRid    = errors.New("blast: missing RID/RTOE field")
)

// Limit is a package level limit on requests that can be sent to the BLAST server. This
// limit is mandated by the BLAST service usage policy. Limit is exported to allow reuse
// of http.Requests provided by RequestWebReadCloser without overrunning the BLAST request limit.
// Changing the the value of Limit to allow more frequent requests may result in IP blocking
// by the BLAST servers.
var Limit = ncbi.NewLimiter(3 * time.Second)

const cmdParam = "CMD" // parameter CMD

// PutParameters is used to pass optional parameters to the Put command. The relevant documentation
// for each of these parameters is at http://www.ncbi.nlm.nih.gov/BLAST/Doc/node9.html.
type PutParameters struct {
	AutoFormat                 string   `param:"AUTO_FORMAT"`
	CompositionBasedStatistics bool     `param:"COMPOSITION_BASED_STATISTICS"`
	Database                   string   `param:"DATABASE"`
	DbGeneticCode              []int    `param:"DB_GENETIC_CODE"`
	EndPoints                  bool     `param:"ENDPOINTS"`
	EntrezQuery                string   `param:"ENTREZ_QUERY"`
	Expect                     *float64 `param:"EXPECT"`
	Filter                     string   `param:"FILTER"`
	GapCosts                   [2]int   `param:"GAPCOSTS"`
	GeneticCode                []int    `param:"GENETIC_CODE"`
	HitListSize                int      `param:"HITLIST_SIZE"`
	IThresh                    float64  `param:"I_THRESH"`
	Layout                     string   `param:"LAYOUT"`
	LCaseMask                  bool     `param:"LCASE_MASK"`
	Megablast                  bool     `param:"MEGABLAST"`
	MatrixName                 string   `param:"MATRIX_NAME"`
	NuclPenalty                int      `param:"NUCL_PENALTY"`
	NuclReward                 int      `param:"NUCL_REWARD"`
	OtherAdvanced              string   `param:"OTHER_ADVANCED"`
	PercIdent                  int      `param:"PERC_IDENT"`
	PhiPattern                 string   `param:"PHI_PATTERN"`
	Program                    string   `param:"PROGRAM"`
	Pssm                       string   `param:"PSSM"`
	QueryFile                  string   `param:"QUERY_FILE"`
	QueryBelieveDefline        bool     `param:"QUERY_BELIEVE_DEFLINE"`
	QueryFrom                  int      `param:"QUERY_FROM"`
	QueryTo                    int      `param:"QUERY_TO"`
	ResultsFile                bool     `param:"RESULTS_FILE"`
	SearchspEff                int      `param:"SEARCHSP_EFF"`
	Service                    string   `param:"SERVICE"`
	Threshold                  int      `param:"THRESHOLD"`
	UngappedAlignment          bool     `param:"UNGAPPED_ALIGNMENT"`
	WordSize                   int      `param:"WORD_SIZE"`
}

// GetParameters is used to pass optional parameters to the Get command. The relevant documentation
// for each of these parameters is at http://www.ncbi.nlm.nih.gov/BLAST/Doc/node9.html.
type GetParameters struct {
	FormatType string `param:"FORMAT_TYPE"` // Ignored by GetOutput: "HTML", "Text", "ASN.1" or "XML".

	Alignments           int     `param:"ALIGNMENTS"`
	AlignmentView        string  `param:"ALIGNMENT_VIEW"`
	Descriptions         int     `param:"DESCRIPTIONS"`
	EntrezLinksNewWindow bool    `param:"ENTREZ_LINKS_NEW_WINDOW"`
	ExpectLow            float64 `param:"EXPECT_LOW"`
	ExpectHigh           float64 `param:"EXPECT_HIGH"`
	FormatEntrezQuery    string  `param:"FORMAT_ENTREZ_QUERY"`
	FormatObject         string  `param:"FORMAT_OBJECT"`
	NcbiGi               bool    `param:"NCBI_GI"`
	ResultsFile          bool    `param:"RESULTS_FILE"`
	Service              string  `param:"SERVICE"`
	ShowOverview         *bool   `param:"SHOW_OVERVIEW"`
}

// WebParameters is used to pass optional parameters to the Web command. The relevant documentation
// for each of these parameters is at http://www.ncbi.nlm.nih.gov/BLAST/Doc/node9.html.
// Note there is inadequate documentation for what parameters the Web command accepts, so all are included.
type WebParameters struct {
	Alignments                 int      `param:"ALIGNMENTS"`
	AlignmentView              string   `param:"ALIGNMENT_VIEW"`
	AutoFormat                 string   `param:"AUTO_FORMAT"`
	Cmd                        string   `param:"CMD"`
	CompositionBasedStatistics bool     `param:"COMPOSITION_BASED_STATISTICS"`
	Database                   string   `param:"DATABASE"`
	DbGeneticCode              []int    `param:"DB_GENETIC_CODE"`
	Descriptions               int      `param:"DESCRIPTIONS"`
	EndPoints                  bool     `param:"ENDPOINTS"`
	EntrezLinksNewWindow       bool     `param:"ENTREZ_LINKS_NEW_WINDOW"`
	EntrezQuery                string   `param:"ENTREZ_QUERY"`
	Expect                     *float64 `param:"EXPECT"`
	ExpectLow                  float64  `param:"EXPECT_LOW"`
	ExpectHigh                 float64  `param:"EXPECT_HIGH"`
	Filter                     string   `param:"FILTER"`
	FormatEntrezQuery          string   `param:"FORMAT_ENTREZ_QUERY"`
	FormatObject               string   `param:"FORMAT_OBJECT"`
	FormatType                 string   `param:"FORMAT_TYPE"`
	GapCosts                   [2]int   `param:"GAPCOSTS"`
	GeneticCode                []int    `param:"GENETIC_CODE"`
	HitListSize                int      `param:"HITLIST_SIZE"`
	IThresh                    float64  `param:"I_THRESH"`
	Layout                     string   `param:"LAYOUT"`
	LCaseMask                  bool     `param:"LCASE_MASK"`
	Megablast                  bool     `param:"MEGABLAST"`
	MatrixName                 string   `param:"MATRIX_NAME"`
	NcbiGi                     bool     `param:"NCBI_GI"`
	NuclPenalty                int      `param:"NUCL_PENALTY"`
	NuclReward                 int      `param:"NUCL_REWARD"`
	OtherAdvanced              string   `param:"OTHER_ADVANCED"`
	PercIdent                  int      `param:"PERC_IDENT"`
	PhiPattern                 string   `param:"PHI_PATTERN"`
	Program                    string   `param:"PROGRAM"`
	Pssm                       string   `param:"PSSM"`
	Query                      string   `param:"QUERY"`
	QueryFile                  string   `param:"QUERY_FILE"`
	QueryBelieveDefline        bool     `param:"QUERY_BELIEVE_DEFLINE"`
	QueryFrom                  int      `param:"QUERY_FROM"`
	QueryTo                    int      `param:"QUERY_TO"`
	Rid                        string   `param:"RID"`
	ResultsFile                bool     `param:"RESULTS_FILE"`
	SearchspEff                int      `param:"SEARCHSP_EFF"`
	Service                    string   `param:"SERVICE"`
	ShowOverview               *bool    `param:"SHOW_OVERVIEW"`
	Threshold                  int      `param:"THRESHOLD"`
	UngappedAlignment          bool     `param:"UNGAPPED_ALIGNMENT"`
	WordSize                   int      `param:"WORD_SIZE"`
}

// BlastUri is the base URL for the NCBI BLAST URL API.
const BlastUri = ncbi.Util("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi")

// fillParams adds elements to v based on the "param" tag of p if the value is not the
// zero value for that type.
func fillParams(cmd string, p interface{}, v url.Values) {
	defer func() {
		v[cmdParam] = []string{cmd}
	}()
	pv := reflect.ValueOf(p)
	if pv.IsNil() {
		return
	}
	pv = pv.Elem()
	n := pv.NumField()
	t := pv.Type()
	for i := 0; i < n; i++ {
		tf := t.Field(i)
		if tf.PkgPath != "" {
			continue
		}
		tag := tf.Tag.Get("param")
		if tag != "" {
			in := pv.Field(i).Interface()
			switch cv := in.(type) {
			case int:
				if cv != 0 {
					v[tag] = []string{fmt.Sprint(cv)}
				}
			case float64:
				if cv != 0 {
					v[tag] = []string{fmt.Sprint(cv)}
				}
			case *float64:
				if cv != nil {
					v[tag] = []string{fmt.Sprint(*cv)}
				}
			case string:
				if cv != "" {
					v[tag] = []string{cv}
				}
			case bool:
				if cv {
					v[tag] = []string{"yes"}
				}
			case [2]int:
				if cv != [2]int{} {
					v[tag] = []string{fmt.Sprintf("%d %d", cv[0], cv[1])}
				}
			case []int:
				if cv != nil {
					s := make([]string, len(cv))
					for i, c := range cv {
						s[i] = fmt.Sprint(c)
					}
					v[tag] = []string{strings.Join(s, ",")}
				}
			case *bool:
				if cv != nil {
					if *cv {
						v[tag] = []string{"yes"}
					} else {
						v[tag] = []string{"no"}
					}
				}
			default:
				panic("cannot reach")
			}
		}
	}
}

// RequestWebReadCloser returns an io.ReadCloser that reads from the stream returned by a Web request
// of the the given page. It is the responsibility of the caller to close the returned stream.
func RequestWebReadCloser(page string, p *WebParameters, tool, email string) (io.ReadCloser, error) {
	v := url.Values{}
	fillParams("Web", p, v)
	if page != "" {
		v["PAGE"] = []string{page}
	}
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

// Put submits a request for a BLAST job to the NCBI BLAST server and returns the associated
// Rid containing the RID for the request.
func Put(query string, p *PutParameters, tool, email string) (*Rid, error) {
	v := url.Values{}
	if query != "" {
		v["QUERY"] = []string{query}
	}
	fillParams("Put", p, v)
	rid := Rid{}
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	defer resp.Close()
	err = rid.unmarshal(resp)
	if err != nil {
		return nil, err
	}
	return &rid, nil
}

// GetOutput returns an Output filled with data obtained from an Get request for the request
// corresponding to r.
func (r *Rid) GetOutput(p *GetParameters, tool, email string) (*Output, error) {
	v := url.Values{}
	if r.rid != "" {
		v["RID"] = []string{r.rid}
	} else {
		return nil, ErrNoRidProvided
	}
	fillParams("Get", p, v)
	v["FORMAT_TYPE"] = []string{"XML"}
	o := Output{}
	r.limit.Wait()
	err := BlastUri.GetXML(v, tool, email, Limit, &o)
	if err != nil {
		return nil, err
	}
	return &o, nil
}

// GetReadCloser returns an io.ReadCloser that reads from the stream returned by a Get request
// corresponding to r. It is the responsibility of the caller to close the returned stream.
func (r *Rid) GetReadCloser(p *GetParameters, tool, email string) (io.ReadCloser, error) {
	v := url.Values{}
	if r.rid != "" {
		v["RID"] = []string{r.rid}
	} else {
		return nil, ErrNoRidProvided
	}
	fillParams("Get", p, v)
	r.limit.Wait()
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

// Delete deletes the the request and results corresponding to r from the NCBI BLAST server.
func (r *Rid) Delete(tool, email string) error {
	v := url.Values{}
	if r.rid != "" {
		v["RID"] = []string{r.rid}
	} else {
		return ErrNoRidProvided
	}
	v[cmdParam] = []string{"Delete"}
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return err
	}
	return resp.Close()
}

// RequestInfo returns an Info with up-to-date information about NCBI BLAST services.
func RequestInfo(target string, tool, email string) (*Info, error) {
	v := url.Values{}
	if target != "" {
		v["TARGET"] = []string{target}
	}
	v[cmdParam] = []string{"Info"}
	var i Info
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	defer resp.Close()
	err = i.unmarshal(resp)
	if err != nil {
		return nil, err
	}
	return &i, nil
}
<commit_msg>Two additional Web parameters<commit_after>// Copyright ©2013 The bíogo.ncbi Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Documentation from http://www.ncbi.nlm.nih.gov/books/NBK21097/

// Package blast provides support for interaction with the NCBI BLAST service.
//
// Please see http://blast.ncbi.nlm.nih.gov/Blast.cgi?CMD=Web&PAGE_TYPE=BlastDocs&DOC_TYPE=DeveloperInfo
// for the BLAST service usage policy.
//
// Required parameters are specified by name in the function call. Optional parameters are
// passed via parameter struct values. See the 'QBlast URL API User's Guide' at
// http://www.ncbi.nlm.nih.gov/BLAST/Doc/urlapi.html for explanation of the use of these
// programs.
//
// The following two parameters should be included in all BLAST requests.
//
//  tool   Name of application making the BLAST call. Its value must be a string with no
//         internal spaces.
//
//  email  E-mail address of the BLAST user. Its value must be a string with no internal
//         spaces, and should be a valid e-mail address.
package blast

import (
	"code.google.com/p/biogo.ncbi"

	"errors"
	"fmt"
	"io"
	"net/url"
	"reflect"
	"strings"
	"time"
)

var (
	ErrNoRidProvided = errors.New("blast: no RID provided")
	ErrMissingRid    = errors.New("blast: missing RID/RTOE field")
)

// Limit is a package level limit on requests that can be sent to the BLAST server. This
// limit is mandated by the BLAST service usage policy. Limit is exported to allow reuse
// of http.Requests provided by RequestWebReadCloser without overrunning the BLAST request limit.
// Changing the the value of Limit to allow more frequent requests may result in IP blocking
// by the BLAST servers.
var Limit = ncbi.NewLimiter(3 * time.Second)

const cmdParam = "CMD" // parameter CMD

// PutParameters is used to pass optional parameters to the Put command. The relevant documentation
// for each of these parameters is at http://www.ncbi.nlm.nih.gov/BLAST/Doc/node9.html.
type PutParameters struct {
	AutoFormat                 string   `param:"AUTO_FORMAT"`
	CompositionBasedStatistics bool     `param:"COMPOSITION_BASED_STATISTICS"`
	Database                   string   `param:"DATABASE"`
	DbGeneticCode              []int    `param:"DB_GENETIC_CODE"`
	EndPoints                  bool     `param:"ENDPOINTS"`
	EntrezQuery                string   `param:"ENTREZ_QUERY"`
	Expect                     *float64 `param:"EXPECT"`
	Filter                     string   `param:"FILTER"`
	GapCosts                   [2]int   `param:"GAPCOSTS"`
	GeneticCode                []int    `param:"GENETIC_CODE"`
	HitListSize                int      `param:"HITLIST_SIZE"`
	IThresh                    float64  `param:"I_THRESH"`
	Layout                     string   `param:"LAYOUT"`
	LCaseMask                  bool     `param:"LCASE_MASK"`
	Megablast                  bool     `param:"MEGABLAST"`
	MatrixName                 string   `param:"MATRIX_NAME"`
	NuclPenalty                int      `param:"NUCL_PENALTY"`
	NuclReward                 int      `param:"NUCL_REWARD"`
	OtherAdvanced              string   `param:"OTHER_ADVANCED"`
	PercIdent                  int      `param:"PERC_IDENT"`
	PhiPattern                 string   `param:"PHI_PATTERN"`
	Program                    string   `param:"PROGRAM"`
	Pssm                       string   `param:"PSSM"`
	QueryFile                  string   `param:"QUERY_FILE"`
	QueryBelieveDefline        bool     `param:"QUERY_BELIEVE_DEFLINE"`
	QueryFrom                  int      `param:"QUERY_FROM"`
	QueryTo                    int      `param:"QUERY_TO"`
	ResultsFile                bool     `param:"RESULTS_FILE"`
	SearchspEff                int      `param:"SEARCHSP_EFF"`
	Service                    string   `param:"SERVICE"`
	Threshold                  int      `param:"THRESHOLD"`
	UngappedAlignment          bool     `param:"UNGAPPED_ALIGNMENT"`
	WordSize                   int      `param:"WORD_SIZE"`
}

// GetParameters is used to pass optional parameters to the Get command. The relevant documentation
// for each of these parameters is at http://www.ncbi.nlm.nih.gov/BLAST/Doc/node9.html.
type GetParameters struct {
	FormatType string `param:"FORMAT_TYPE"` // Ignored by GetOutput: "HTML", "Text", "ASN.1" or "XML".

	Alignments           int     `param:"ALIGNMENTS"`
	AlignmentView        string  `param:"ALIGNMENT_VIEW"`
	Descriptions         int     `param:"DESCRIPTIONS"`
	EntrezLinksNewWindow bool    `param:"ENTREZ_LINKS_NEW_WINDOW"`
	ExpectLow            float64 `param:"EXPECT_LOW"`
	ExpectHigh           float64 `param:"EXPECT_HIGH"`
	FormatEntrezQuery    string  `param:"FORMAT_ENTREZ_QUERY"`
	FormatObject         string  `param:"FORMAT_OBJECT"`
	NcbiGi               bool    `param:"NCBI_GI"`
	ResultsFile          bool    `param:"RESULTS_FILE"`
	Service              string  `param:"SERVICE"`
	ShowOverview         *bool   `param:"SHOW_OVERVIEW"`
}

// WebParameters is used to pass optional parameters to the Web command. The relevant documentation
// for each of these parameters is at http://www.ncbi.nlm.nih.gov/BLAST/Doc/node9.html.
// Note there is inadequate documentation for what parameters the Web command accepts, so all are included.
type WebParameters struct {
	Alignments                 int      `param:"ALIGNMENTS"`
	AlignmentView              string   `param:"ALIGNMENT_VIEW"`
	AutoFormat                 string   `param:"AUTO_FORMAT"`
	Cmd                        string   `param:"CMD"`
	CompositionBasedStatistics bool     `param:"COMPOSITION_BASED_STATISTICS"`
	Database                   string   `param:"DATABASE"`
	DbGeneticCode              []int    `param:"DB_GENETIC_CODE"`
	Descriptions               int      `param:"DESCRIPTIONS"`
	DocType                    string   `param:"DOC_TYPE"`
	EndPoints                  bool     `param:"ENDPOINTS"`
	EntrezLinksNewWindow       bool     `param:"ENTREZ_LINKS_NEW_WINDOW"`
	EntrezQuery                string   `param:"ENTREZ_QUERY"`
	Expect                     *float64 `param:"EXPECT"`
	ExpectLow                  float64  `param:"EXPECT_LOW"`
	ExpectHigh                 float64  `param:"EXPECT_HIGH"`
	Filter                     string   `param:"FILTER"`
	FormatEntrezQuery          string   `param:"FORMAT_ENTREZ_QUERY"`
	FormatObject               string   `param:"FORMAT_OBJECT"`
	FormatType                 string   `param:"FORMAT_TYPE"`
	GapCosts                   [2]int   `param:"GAPCOSTS"`
	GeneticCode                []int    `param:"GENETIC_CODE"`
	HitListSize                int      `param:"HITLIST_SIZE"`
	IThresh                    float64  `param:"I_THRESH"`
	Layout                     string   `param:"LAYOUT"`
	LCaseMask                  bool     `param:"LCASE_MASK"`
	Megablast                  bool     `param:"MEGABLAST"`
	MatrixName                 string   `param:"MATRIX_NAME"`
	NcbiGi                     bool     `param:"NCBI_GI"`
	NuclPenalty                int      `param:"NUCL_PENALTY"`
	NuclReward                 int      `param:"NUCL_REWARD"`
	OtherAdvanced              string   `param:"OTHER_ADVANCED"`
	PageType                   string   `param:"PAGE_TYPE"`
	PercIdent                  int      `param:"PERC_IDENT"`
	PhiPattern                 string   `param:"PHI_PATTERN"`
	Program                    string   `param:"PROGRAM"`
	Pssm                       string   `param:"PSSM"`
	Query                      string   `param:"QUERY"`
	QueryFile                  string   `param:"QUERY_FILE"`
	QueryBelieveDefline        bool     `param:"QUERY_BELIEVE_DEFLINE"`
	QueryFrom                  int      `param:"QUERY_FROM"`
	QueryTo                    int      `param:"QUERY_TO"`
	Rid                        string   `param:"RID"`
	ResultsFile                bool     `param:"RESULTS_FILE"`
	SearchspEff                int      `param:"SEARCHSP_EFF"`
	Service                    string   `param:"SERVICE"`
	ShowOverview               *bool    `param:"SHOW_OVERVIEW"`
	Threshold                  int      `param:"THRESHOLD"`
	UngappedAlignment          bool     `param:"UNGAPPED_ALIGNMENT"`
	WordSize                   int      `param:"WORD_SIZE"`
}

// BlastUri is the base URL for the NCBI BLAST URL API.
const BlastUri = ncbi.Util("http://www.ncbi.nlm.nih.gov/blast/Blast.cgi")

// fillParams adds elements to v based on the "param" tag of p if the value is not the
// zero value for that type.
func fillParams(cmd string, p interface{}, v url.Values) {
	defer func() {
		v[cmdParam] = []string{cmd}
	}()
	pv := reflect.ValueOf(p)
	if pv.IsNil() {
		return
	}
	pv = pv.Elem()
	n := pv.NumField()
	t := pv.Type()
	for i := 0; i < n; i++ {
		tf := t.Field(i)
		if tf.PkgPath != "" {
			continue
		}
		tag := tf.Tag.Get("param")
		if tag != "" {
			in := pv.Field(i).Interface()
			switch cv := in.(type) {
			case int:
				if cv != 0 {
					v[tag] = []string{fmt.Sprint(cv)}
				}
			case float64:
				if cv != 0 {
					v[tag] = []string{fmt.Sprint(cv)}
				}
			case *float64:
				if cv != nil {
					v[tag] = []string{fmt.Sprint(*cv)}
				}
			case string:
				if cv != "" {
					v[tag] = []string{cv}
				}
			case bool:
				if cv {
					v[tag] = []string{"yes"}
				}
			case [2]int:
				if cv != [2]int{} {
					v[tag] = []string{fmt.Sprintf("%d %d", cv[0], cv[1])}
				}
			case []int:
				if cv != nil {
					s := make([]string, len(cv))
					for i, c := range cv {
						s[i] = fmt.Sprint(c)
					}
					v[tag] = []string{strings.Join(s, ",")}
				}
			case *bool:
				if cv != nil {
					if *cv {
						v[tag] = []string{"yes"}
					} else {
						v[tag] = []string{"no"}
					}
				}
			default:
				panic("cannot reach")
			}
		}
	}
}

// RequestWebReadCloser returns an io.ReadCloser that reads from the stream returned by a Web request
// of the the given page. It is the responsibility of the caller to close the returned stream.
func RequestWebReadCloser(page string, p *WebParameters, tool, email string) (io.ReadCloser, error) {
	v := url.Values{}
	fillParams("Web", p, v)
	if page != "" {
		v["PAGE"] = []string{page}
	}
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

// Put submits a request for a BLAST job to the NCBI BLAST server and returns the associated
// Rid containing the RID for the request.
func Put(query string, p *PutParameters, tool, email string) (*Rid, error) {
	v := url.Values{}
	if query != "" {
		v["QUERY"] = []string{query}
	}
	fillParams("Put", p, v)
	rid := Rid{}
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	defer resp.Close()
	err = rid.unmarshal(resp)
	if err != nil {
		return nil, err
	}
	return &rid, nil
}

// GetOutput returns an Output filled with data obtained from an Get request for the request
// corresponding to r.
func (r *Rid) GetOutput(p *GetParameters, tool, email string) (*Output, error) {
	v := url.Values{}
	if r.rid != "" {
		v["RID"] = []string{r.rid}
	} else {
		return nil, ErrNoRidProvided
	}
	fillParams("Get", p, v)
	v["FORMAT_TYPE"] = []string{"XML"}
	o := Output{}
	r.limit.Wait()
	err := BlastUri.GetXML(v, tool, email, Limit, &o)
	if err != nil {
		return nil, err
	}
	return &o, nil
}

// GetReadCloser returns an io.ReadCloser that reads from the stream returned by a Get request
// corresponding to r. It is the responsibility of the caller to close the returned stream.
func (r *Rid) GetReadCloser(p *GetParameters, tool, email string) (io.ReadCloser, error) {
	v := url.Values{}
	if r.rid != "" {
		v["RID"] = []string{r.rid}
	} else {
		return nil, ErrNoRidProvided
	}
	fillParams("Get", p, v)
	r.limit.Wait()
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

// Delete deletes the the request and results corresponding to r from the NCBI BLAST server.
func (r *Rid) Delete(tool, email string) error {
	v := url.Values{}
	if r.rid != "" {
		v["RID"] = []string{r.rid}
	} else {
		return ErrNoRidProvided
	}
	v[cmdParam] = []string{"Delete"}
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return err
	}
	return resp.Close()
}

// RequestInfo returns an Info with up-to-date information about NCBI BLAST services.
func RequestInfo(target string, tool, email string) (*Info, error) {
	v := url.Values{}
	if target != "" {
		v["TARGET"] = []string{target}
	}
	v[cmdParam] = []string{"Info"}
	var i Info
	resp, err := BlastUri.Get(v, tool, email, Limit)
	if err != nil {
		return nil, err
	}
	defer resp.Close()
	err = i.unmarshal(resp)
	if err != nil {
		return nil, err
	}
	return &i, nil
}
<|endoftext|>


<commit_before>package neutrino

import (
	"sync"
	"time"

	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btclog"
)

// blockProgressLogger provides periodic logging for other services in order
// to show users progress of certain "actions" involving some or all current
// blocks. Ex: syncing to best chain, indexing all blocks, etc.
type blockProgressLogger struct {
	receivedLogBlocks int64
	lastBlockLogTime  time.Time

	subsystemLogger btclog.Logger
	progressAction  string
	sync.Mutex
}

// newBlockProgressLogger returns a new block progress logger.
// The progress message is templated as follows:
//  {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
//  ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
	return &blockProgressLogger{
		lastBlockLogTime: time.Now(),
		progressAction:   progressMessage,
		subsystemLogger:  logger,
	}
}

// LogBlockHeight logs a new block height as an information message to show
// progress to the user. In order to prevent spam, it limits logging to one
// message every 10 seconds with duration and totals included.
func (b *blockProgressLogger) LogBlockHeight(header *wire.BlockHeader, height int32) {
	b.Lock()
	defer b.Unlock()

	b.receivedLogBlocks++

	// TODO(roasbeef): have diff logger for fetching blocks to can eye ball
	// false positive

	now := time.Now()
	duration := now.Sub(b.lastBlockLogTime)
	if duration < time.Second*10 {
		return
	}

	// Truncate the duration to 10s of milliseconds.
	durationMillis := int64(duration / time.Millisecond)
	tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)

	// Log information about new block height.
	blockStr := "blocks"
	if b.receivedLogBlocks == 1 {
		blockStr = "block"
	}
	b.subsystemLogger.Infof("%s %d %s in the last %s (height %d, %s)",
		b.progressAction, b.receivedLogBlocks, blockStr, tDuration,
		height, header.Timestamp)

	b.receivedLogBlocks = 0
	b.lastBlockLogTime = now
}

func (b *blockProgressLogger) SetLastLogTime(time time.Time) {
	b.lastBlockLogTime = time
}
<commit_msg>blocklogger: refactor block logger to be a generic "entity progress" logger<commit_after>package neutrino

import (
	"sync"
	"time"

	"github.com/btcsuite/btclog"
)

// blockProgressLogger provides periodic logging for other services in order
// to show users progress of certain "actions" involving some or all current
// blocks. Ex: syncing to best chain, indexing all blocks, etc.
type blockProgressLogger struct {
	receivedLogBlocks int64
	lastBlockLogTime  time.Time

	entityType string

	subsystemLogger btclog.Logger
	progressAction  string
	sync.Mutex
}

// newBlockProgressLogger returns a new block progress logger.
// The progress message is templated as follows:
//  {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
//  ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
func newBlockProgressLogger(progressMessage string,
	entityType string, logger btclog.Logger) *blockProgressLogger {

	return &blockProgressLogger{
		entityType:       entityType,
		lastBlockLogTime: time.Now(),
		progressAction:   progressMessage,
		subsystemLogger:  logger,
	}
}

// LogBlockHeight logs a new block height as an information message to show
// progress to the user. In order to prevent spam, it limits logging to one
// message every 10 seconds with duration and totals included.
func (b *blockProgressLogger) LogBlockHeight(timestamp time.Time, height int32) {
	b.Lock()
	defer b.Unlock()

	b.receivedLogBlocks++

	// TODO(roasbeef): have diff logger for fetching blocks to can eye ball
	// false positive

	now := time.Now()
	duration := now.Sub(b.lastBlockLogTime)
	if duration < time.Second*10 {
		return
	}

	// Truncate the duration to 10s of milliseconds.
	durationMillis := int64(duration / time.Millisecond)
	tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)

	// Log information about new block height.
	entityStr := b.entityType
	if b.receivedLogBlocks > 1 {
		entityStr += "s"
	}
	b.subsystemLogger.Infof("%s %d %s in the last %s (height %d, %s)",
		b.progressAction, b.receivedLogBlocks, entityStr, tDuration,
		height, timestamp)

	b.receivedLogBlocks = 0
	b.lastBlockLogTime = now
}

func (b *blockProgressLogger) SetLastLogTime(time time.Time) {
	b.lastBlockLogTime = time
}
<|endoftext|>