<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"github.com/heroku/slog"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"
)

type Payload struct {
	SourceAddr string
	RequestId  string
	Body       []byte
	WaitCh     chan bool
}

type FixerFunc func(io.Reader, slog.Context, string, string) ([]byte, error)

type HttpServer struct {
	Config         *IssConfig
	FixerFunc      FixerFunc
	Outlet         chan *Payload
	InFlightWg     sync.WaitGroup
	ShutdownCh     ShutdownCh
	isShuttingDown bool
}

func NewHttpServer(config *IssConfig, fixerFunc FixerFunc, outlet chan *Payload) *HttpServer {
	return &HttpServer{
		Config:         config,
		FixerFunc:      fixerFunc,
		Outlet:         outlet,
		ShutdownCh:     make(chan int),
		isShuttingDown: false,
	}
}

func (s *HttpServer) Run() error {
	go s.awaitShutdown()

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		ctx := slog.Context{}
		defer func() { LogContext(ctx) }()

		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		// check outlet depth?
		ctx.Count("log-iss.http.health.get", 1)
	})

	http.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
		ctx := slog.Context{}
		defer func() { LogContext(ctx) }()

		if s.Config.EnforceSsl && r.Header.Get("X-Forwarded-Proto") != "https" {
			http.Error(w, "Only SSL requests accepted", 400)
			return
		}

		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		if r.Method != "POST" {
			http.Error(w, "Only POST is accepted", 400)
			return
		}

		if r.Header.Get("Content-Type") != "application/logplex-1" {
			http.Error(w, "Only Content-Type application/logplex-1 is accepted", 400)
			return
		}

		if err := s.checkAuth(r); err != nil {
			http.Error(w, err.Error(), 401)
			return
		}

		defer ctx.MeasureSince("log-iss.http.logs.post.duration", time.Now())

		remoteAddr := r.Header.Get("X-Forwarded-For")
		if remoteAddr == "" {
			remoteAddrParts := strings.Split(r.RemoteAddr, ":")
			remoteAddr = strings.Join(remoteAddrParts[:len(remoteAddrParts)-1], ":")
		}
		ctx.Add("remote_addr", remoteAddr)

		requestId := r.Header.Get("X-Request-Id")
		ctx.Add("request_id", requestId)

		logplexDrainToken := r.Header.Get("Logplex-Drain-Token")
		ctx.Add("logdrain_token", logplexDrainToken)

		if err, status := s.process(r.Body, ctx, remoteAddr, requestId, logplexDrainToken); err != nil {
			http.Error(w, err.Error(), status)
			ctx.Count("log-iss.http.logs.post.error", 1)
			ctx.Add("post.error", err)
			ctx.Add("status", status)
			return
		}

		ctx.Add("log-iss.http.logs.post.success", 1)
	})

	if err := http.ListenAndServe(":"+s.Config.HttpPort, nil); err != nil {
		return err
	}

	return nil
}

func (s *HttpServer) awaitShutdown() {
	<-s.ShutdownCh
	Logf("ns=http at=shutdown")
	s.isShuttingDown = true
}

func (s *HttpServer) checkAuth(r *http.Request) error {
	header := r.Header.Get("Authorization")
	if header == "" {
		return errors.New("Authorization required")
	}
	headerParts := strings.SplitN(header, " ", 2)
	if len(headerParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	method := headerParts[0]
	if method != "Basic" {
		return errors.New("Only Basic Authorization is accepted")
	}

	encodedUserPass := headerParts[1]
	decodedUserPass, err := base64.StdEncoding.DecodeString(encodedUserPass)
	if err != nil {
		return errors.New("Authorization header is malformed")
	}

	userPassParts := bytes.SplitN(decodedUserPass, []byte{':'}, 2)
	if len(userPassParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	user := userPassParts[0]
	pass := userPassParts[1]
	token, ok := s.Config.Tokens[string(user)]
	if !ok {
		return errors.New("Unknown user")
	}
	if token != string(pass) {
		return errors.New("Incorrect token")
	}

	return nil
}

func (s *HttpServer) process(r io.Reader, ctx slog.Context, remoteAddr string, requestId string, logplexDrainToken string) (error, int) {
	s.InFlightWg.Add(1)
	defer s.InFlightWg.Done()

	var start time.Time

	fixedBody, err := s.FixerFunc(r, ctx, remoteAddr, logplexDrainToken)
	if err != nil {
		return errors.New("Problem processing body"), 400
	}

	waitCh := make(chan bool, 1)
	deadlineCh := time.After(time.Duration(5) * time.Second)

	start = time.Now()
	select {
	case s.Outlet <- &Payload{remoteAddr, requestId, fixedBody, waitCh}:
	case <-deadlineCh:
		ctx.Count("log-iss.http.logs.send.error", 1)
		return errors.New("Timeout delivering message"), 504
	}
	ctx.MeasureSince("log-iss.http.logs.send.duration", start)

	start = time.Now()
	select {
	case <-waitCh:
	case <-deadlineCh:
		ctx.Count("log-iss.http.logs.wait.error", 1)
		return errors.New("Timeout delivering message"), 504
	}
	ctx.MeasureSince("log-iss.http.logs.wait.duration", start)

	return nil, 200
}
<commit_msg>DRY up http error handling<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"github.com/heroku/slog"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"
)

type Payload struct {
	SourceAddr string
	RequestId  string
	Body       []byte
	WaitCh     chan bool
}

type FixerFunc func(io.Reader, slog.Context, string, string) ([]byte, error)

type HttpServer struct {
	Config         *IssConfig
	FixerFunc      FixerFunc
	Outlet         chan *Payload
	InFlightWg     sync.WaitGroup
	ShutdownCh     ShutdownCh
	isShuttingDown bool
}

func NewHttpServer(config *IssConfig, fixerFunc FixerFunc, outlet chan *Payload) *HttpServer {
	return &HttpServer{
		Config:         config,
		FixerFunc:      fixerFunc,
		Outlet:         outlet,
		ShutdownCh:     make(chan int),
		isShuttingDown: false,
	}
}

func handleHTTPError(ctx slog.Context, w http.ResponseWriter, errMsg string, errCode int) {
	ctx.Count("log-iss.http.logs.post.error", 1)
	ctx.Add("post.error", errMsg)
	ctx.Add("post.code", errCode)
	http.Error(w, errMsg, errCode)
}

func (s *HttpServer) Run() error {
	go s.awaitShutdown()

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		ctx := slog.Context{}
		defer func() { LogContext(ctx) }()

		if s.isShuttingDown {
			http.Error(w, "Shutting down", 503)
			return
		}

		// check outlet depth?
		ctx.Count("log-iss.http.health.get", 1)
	})

	http.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
		ctx := slog.Context{}
		defer func() { LogContext(ctx) }()
		defer ctx.MeasureSince("log-iss.http.logs.post.duration", time.Now())

		if s.Config.EnforceSsl && r.Header.Get("X-Forwarded-Proto") != "https" {
			handleHTTPError(ctx, w, "Only SSL requests accepted", 400)
			return
		}

		if s.isShuttingDown {
			handleHTTPError(ctx, w, "Shutting down", 503)
			return
		}

		if r.Method != "POST" {
			handleHTTPError(ctx, w, "Only POST is accepted", 400)
			return
		}

		if r.Header.Get("Content-Type") != "application/logplex-1" {
			handleHTTPError(ctx, w, "Only Content-Type application/logplex-1 is accepted", 400)
			return
		}

		if err := s.checkAuth(r); err != nil {
			handleHTTPError(ctx, w, err.Error(), 401)
			return
		}

		remoteAddr := r.Header.Get("X-Forwarded-For")
		if remoteAddr == "" {
			remoteAddrParts := strings.Split(r.RemoteAddr, ":")
			remoteAddr = strings.Join(remoteAddrParts[:len(remoteAddrParts)-1], ":")
		}
		ctx.Add("remote_addr", remoteAddr)

		requestId := r.Header.Get("X-Request-Id")
		ctx.Add("request_id", requestId)

		logplexDrainToken := r.Header.Get("Logplex-Drain-Token")
		ctx.Add("logdrain_token", logplexDrainToken)

		if err, status := s.process(r.Body, ctx, remoteAddr, requestId, logplexDrainToken); err != nil {
			handleHTTPError(ctx, w, err.Error(), status)
			return
		}

		ctx.Add("log-iss.http.logs.post.success", 1)
	})

	if err := http.ListenAndServe(":"+s.Config.HttpPort, nil); err != nil {
		return err
	}

	return nil
}

func (s *HttpServer) awaitShutdown() {
	<-s.ShutdownCh
	Logf("ns=http at=shutdown")
	s.isShuttingDown = true
}

func (s *HttpServer) checkAuth(r *http.Request) error {
	header := r.Header.Get("Authorization")
	if header == "" {
		return errors.New("Authorization required")
	}
	headerParts := strings.SplitN(header, " ", 2)
	if len(headerParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	method := headerParts[0]
	if method != "Basic" {
		return errors.New("Only Basic Authorization is accepted")
	}

	encodedUserPass := headerParts[1]
	decodedUserPass, err := base64.StdEncoding.DecodeString(encodedUserPass)
	if err != nil {
		return errors.New("Authorization header is malformed")
	}

	userPassParts := bytes.SplitN(decodedUserPass, []byte{':'}, 2)
	if len(userPassParts) != 2 {
		return errors.New("Authorization header is malformed")
	}

	user := userPassParts[0]
	pass := userPassParts[1]
	token, ok := s.Config.Tokens[string(user)]
	if !ok {
		return errors.New("Unknown user")
	}
	if token != string(pass) {
		return errors.New("Incorrect token")
	}

	return nil
}

func (s *HttpServer) process(r io.Reader, ctx slog.Context, remoteAddr string, requestId string, logplexDrainToken string) (error, int) {
	s.InFlightWg.Add(1)
	defer s.InFlightWg.Done()

	var start time.Time

	fixedBody, err := s.FixerFunc(r, ctx, remoteAddr, logplexDrainToken)
	if err != nil {
		return errors.New("Problem processing body"), 400
	}

	waitCh := make(chan bool, 1)
	deadlineCh := time.After(time.Duration(5) * time.Second)

	start = time.Now()
	select {
	case s.Outlet <- &Payload{remoteAddr, requestId, fixedBody, waitCh}:
	case <-deadlineCh:
		ctx.Count("log-iss.http.logs.send.error", 1)
		return errors.New("Timeout delivering message"), 504
	}
	ctx.MeasureSince("log-iss.http.logs.send.duration", start)

	start = time.Now()
	select {
	case <-waitCh:
	case <-deadlineCh:
		ctx.Count("log-iss.http.logs.wait.error", 1)
		return errors.New("Timeout delivering message"), 504
	}
	ctx.MeasureSince("log-iss.http.logs.wait.duration", start)

	return nil, 200
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"code.google.com/p/go.net/context"

	"github.com/livegrep/livegrep/client"
	"github.com/livegrep/livegrep/server/api"
	"github.com/livegrep/livegrep/server/log"
)

func replyJSON(ctx context.Context, w http.ResponseWriter, status int, obj interface{}) {
	w.WriteHeader(status)
	enc := json.NewEncoder(w)
	if err := enc.Encode(obj); err != nil {
		log.Printf(ctx, "writing http response, data=%s err=%s",
			asJSON{obj},
			err.Error())
	}
}

func writeError(ctx context.Context, w http.ResponseWriter, status int, code, message string) {
	log.Printf(ctx, "error status=%d code=%s message=%s",
		status, code, message)
	replyJSON(ctx, w, status, &api.ReplyError{Err: api.InnerError{Code: code, Message: message}})
}

func writeQueryError(ctx context.Context, w http.ResponseWriter, err error) {
	if qe, ok := err.(client.QueryError); ok {
		writeError(ctx, w, 400, "query_error", qe.Err)
	} else {
		writeError(ctx, w, 500, "internal_error",
			fmt.Sprintf("Talking to backend: %s", err.Error()))
	}
	return
}

func extractQuery(ctx context.Context, r *http.Request) client.Query {
	params := r.URL.Query()
	var query client.Query
	if q, ok := params["q"]; ok {
		query = ParseQuery(q[0])
		log.Printf(ctx, "parsing query q=%v out=%s", q[0], asJSON{query})
	}
	if line, ok := params["line"]; ok {
		query.Line = line[0]
	}
	if file, ok := params["file"]; ok {
		query.File = file[0]
	}
	if repo, ok := params["repo"]; ok {
		query.Repo = repo[0]
	}
	if fc, ok := params["fold_case"]; ok && fc[0] != "" {
		query.FoldCase = true
	}
	return query
}

const MaxRetries = 8

var (
	ErrTimedOut = errors.New("timed out talking to backend")
)

func (s *server) doSearch(ctx context.Context, backend *Backend, q *client.Query) (*api.ReplySearch, error) {
	var cl client.Client
	var search client.Search
	var err error

	select {
	case cl = <-backend.Clients:
	case <-ctx.Done():
		return nil, ErrTimedOut
	}
	defer backend.CheckIn(cl)

	search, err = cl.Query(q)
	if err != nil {
		log.Printf(ctx, "error talking to backend err=%s", err)
		return nil, err
	}

	reply := &api.ReplySearch{Results: make([]*client.Result, 0)}

	for r := range search.Results() {
		reply.Results = append(reply.Results, r)
	}

	reply.Info, err = search.Close()
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *server) ServeAPISearch(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	backendName := r.URL.Query().Get(":backend")
	var backend *Backend
	if backendName != "" {
		backend = s.bk[backendName]
		if backend == nil {
			writeError(ctx, w, 400, "bad_backend",
				fmt.Sprintf("Unknown backend: %s", backendName))
			return
		}
	} else {
		for _, backend = range s.bk {
			break
		}
	}

	q := extractQuery(ctx, r)

	if q.Line == "" {
		writeError(ctx, w, 400, "bad_query",
			"You must specify a 'line' regex.")
		return
	}

	var reply *api.ReplySearch
	var err error

	for tries := 0; tries < MaxRetries; tries++ {
		reply, err = s.doSearch(ctx, backend, &q)
		if err == nil {
			break
		}
		if err == ErrTimedOut {
			break
		}
		if _, ok := err.(client.QueryError); ok {
			break
		}
		log.Printf(ctx, "error querying try=%d err=%q", tries, err)
	}

	if err != nil {
		log.Printf(ctx, "error in search err=%s", err)
		writeQueryError(ctx, w, err)
		return
	}

	log.Printf(ctx,
		"responding success results=%d why=%s",
		len(reply.Results),
		reply.Info.ExitReason)

	replyJSON(ctx, w, 200, reply)
}
<commit_msg>Log query time details.<commit_after>package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"code.google.com/p/go.net/context"

	"github.com/livegrep/livegrep/client"
	"github.com/livegrep/livegrep/server/api"
	"github.com/livegrep/livegrep/server/log"
)

func replyJSON(ctx context.Context, w http.ResponseWriter, status int, obj interface{}) {
	w.WriteHeader(status)
	enc := json.NewEncoder(w)
	if err := enc.Encode(obj); err != nil {
		log.Printf(ctx, "writing http response, data=%s err=%s",
			asJSON{obj},
			err.Error())
	}
}

func writeError(ctx context.Context, w http.ResponseWriter, status int, code, message string) {
	log.Printf(ctx, "error status=%d code=%s message=%s",
		status, code, message)
	replyJSON(ctx, w, status, &api.ReplyError{Err: api.InnerError{Code: code, Message: message}})
}

func writeQueryError(ctx context.Context, w http.ResponseWriter, err error) {
	if qe, ok := err.(client.QueryError); ok {
		writeError(ctx, w, 400, "query_error", qe.Err)
	} else {
		writeError(ctx, w, 500, "internal_error",
			fmt.Sprintf("Talking to backend: %s", err.Error()))
	}
	return
}

func extractQuery(ctx context.Context, r *http.Request) client.Query {
	params := r.URL.Query()
	var query client.Query
	if q, ok := params["q"]; ok {
		query = ParseQuery(q[0])
		log.Printf(ctx, "parsing query q=%v out=%s", q[0], asJSON{query})
	}
	if line, ok := params["line"]; ok {
		query.Line = line[0]
	}
	if file, ok := params["file"]; ok {
		query.File = file[0]
	}
	if repo, ok := params["repo"]; ok {
		query.Repo = repo[0]
	}
	if fc, ok := params["fold_case"]; ok && fc[0] != "" {
		query.FoldCase = true
	}
	return query
}

const MaxRetries = 8

var (
	ErrTimedOut = errors.New("timed out talking to backend")
)

func (s *server) doSearch(ctx context.Context, backend *Backend, q *client.Query) (*api.ReplySearch, error) {
	var cl client.Client
	var search client.Search
	var err error

	select {
	case cl = <-backend.Clients:
	case <-ctx.Done():
		return nil, ErrTimedOut
	}
	defer backend.CheckIn(cl)

	search, err = cl.Query(q)
	if err != nil {
		log.Printf(ctx, "error talking to backend err=%s", err)
		return nil, err
	}

	reply := &api.ReplySearch{Results: make([]*client.Result, 0)}

	for r := range search.Results() {
		reply.Results = append(reply.Results, r)
	}

	reply.Info, err = search.Close()
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *server) ServeAPISearch(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	backendName := r.URL.Query().Get(":backend")
	var backend *Backend
	if backendName != "" {
		backend = s.bk[backendName]
		if backend == nil {
			writeError(ctx, w, 400, "bad_backend",
				fmt.Sprintf("Unknown backend: %s", backendName))
			return
		}
	} else {
		for _, backend = range s.bk {
			break
		}
	}

	q := extractQuery(ctx, r)

	if q.Line == "" {
		writeError(ctx, w, 400, "bad_query",
			"You must specify a 'line' regex.")
		return
	}

	var reply *api.ReplySearch
	var err error

	for tries := 0; tries < MaxRetries; tries++ {
		reply, err = s.doSearch(ctx, backend, &q)
		if err == nil {
			break
		}
		if err == ErrTimedOut {
			break
		}
		if _, ok := err.(client.QueryError); ok {
			break
		}
		log.Printf(ctx, "error querying try=%d err=%q", tries, err)
	}

	if err != nil {
		log.Printf(ctx, "error in search err=%s", err)
		writeQueryError(ctx, w, err)
		return
	}

	log.Printf(ctx,
		"responding success results=%d why=%s stats=%s",
		len(reply.Results),
		reply.Info.ExitReason,
		asJSON{reply.Info})

	replyJSON(ctx, w, 200, reply)
}
<|endoftext|>


<commit_before>/*
   Package edgeworth is a second generation iris core.

   It provides instruction decoding, execution, unparsing, and mnemonic
   generation.

   Edgeworth extends the iris architecture to 24-bits and is a modified harvard
   architecture where data and code can be used somewhat interchangeably
*/
package edgeworth

import "fmt"
type Instruction uint64
type ControlBits uint16
type RegisterIndex byte
type Datum uint32
type GetRegisterIndexError struct {
      IndexProvided int
}

func (e *GetRegisterIndexError) Error() string {
   return fmt.Sprintf("Register index %d is out of range!", e.IndexProvided)
}

func (inst Instruction) GetControlBits() ControlBits {
   return ControlBits(inst & 0x000000000000FFFF)
}

func (inst Instruction) GetRegisterIndex(index int) (RegisterIndex, error) {
   var a Instruction
   a = (inst & 0xFFFFFFFFFFFF0000) >> 16
   if index >= 0 && index < 6  {
      return RegisterIndex(a>>(8*Instruction(index))), nil
   } else {
      return 0, &GetRegisterIndexError {
         index,
      }
   }
}

func (d Datum) GetTagBits() byte {
   return byte((d & 0xFF000000) >> 24)
}

func (d Datum) GetValue() Datum {
   return Datum(d & 0x00FFFFFF)
}

<commit_msg>Started defining the core<commit_after>/*
   Package edgeworth is a second generation iris core.

   It provides instruction decoding, execution, unparsing, and mnemonic
   generation.

   Edgeworth extends the iris architecture to 32-bits and is a modified harvard
   architecture where data and code can be used somewhat interchangeably
*/
package edgeworth

import "fmt"

const (
	RegisterCount    = 256
	InstructionCount = 16777216
	DataCount        = 16777216
)

type Instruction uint64
type ControlBits uint16
type RegisterIndex byte
type Datum uint32
type Register uint32

type Core struct {
	registers [RegisterCount]Register
	data      [DataCount]Datum
	code      [InstructionCount]Instruction
}
type GetRegisterIndexError struct {
	IndexProvided int
}

func (e *GetRegisterIndexError) Error() string {
	return fmt.Sprintf("Register index %d is out of range!", e.IndexProvided)
}

func (inst Instruction) GetControlBits() ControlBits {
	return ControlBits(inst & 0x000000000000FFFF)
}

func (inst Instruction) GetRegisterIndex(index int) (RegisterIndex, error) {
	var a Instruction
	a = (inst & 0xFFFFFFFFFFFF0000) >> 16
	if index >= 0 && index < 6 {
		return RegisterIndex(a >> (8 * Instruction(index))), nil
	} else {
		return 0, &GetRegisterIndexError{
			index,
		}
	}
}

func (d Datum) GetTagBits() byte {
	return byte((d & 0xFF000000) >> 24)
}

func (d Datum) GetValue() Datum {
	return Datum(d & 0x00FFFFFF)
}
<|endoftext|>


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

package gzip

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"gitea.com/macaron/macaron"
	"github.com/klauspost/compress/gzip"
)

const (
	acceptEncodingHeader  = "Accept-Encoding"
	contentEncodingHeader = "Content-Encoding"
	contentLengthHeader   = "Content-Length"
	contentTypeHeader     = "Content-Type"
	rangeHeader           = "Range"
	varyHeader            = "Vary"
)

const (
	// MinSize is the minimum size of content we will compress
	MinSize = 1400
)

// noopClosers are io.Writers with a shim to prevent early closure
type noopCloser struct {
	io.Writer
}

func (noopCloser) Close() error { return nil }

// WriterPool is a gzip writer pool to reduce workload on creation of
// gzip writers
type WriterPool struct {
	pool             sync.Pool
	compressionLevel int
}

// NewWriterPool creates a new pool
func NewWriterPool(compressionLevel int) *WriterPool {
	return &WriterPool{pool: sync.Pool{
		// New will return nil, we'll manage the creation of new
		// writers in the middleware
		New: func() interface{} { return nil },
	},
		compressionLevel: compressionLevel}
}

// Get a writer from the pool - or create one if not available
func (wp *WriterPool) Get(rw macaron.ResponseWriter) *gzip.Writer {
	ret := wp.pool.Get()
	if ret == nil {
		ret, _ = gzip.NewWriterLevel(rw, wp.compressionLevel)
	} else {
		ret.(*gzip.Writer).Reset(rw)
	}
	return ret.(*gzip.Writer)
}

// Put returns a writer to the pool
func (wp *WriterPool) Put(w *gzip.Writer) {
	wp.pool.Put(w)
}

var writerPool WriterPool

// Options represents the configuration for the gzip middleware
type Options struct {
	CompressionLevel int
}

func validateCompressionLevel(level int) bool {
	return level == gzip.DefaultCompression ||
		level == gzip.ConstantCompression ||
		(level >= gzip.BestSpeed && level <= gzip.BestCompression)
}

func validate(options []Options) Options {
	// Default to level 4 compression (Best results seem to be between 4 and 6)
	opt := Options{CompressionLevel: 4}
	if len(options) > 0 {
		opt = options[0]
	}
	if !validateCompressionLevel(opt.CompressionLevel) {
		opt.CompressionLevel = 4
	}
	return opt
}

// Middleware creates a macaron.Handler to proxy the response
func Middleware(options ...Options) macaron.Handler {
	opt := validate(options)
	writerPool = *NewWriterPool(opt.CompressionLevel)
	regex := regexp.MustCompile(`bytes=(\d+)\-.*`)

	return func(ctx *macaron.Context) {
		// If the client won't accept gzip or x-gzip don't compress
		if !strings.Contains(ctx.Req.Header.Get(acceptEncodingHeader), "gzip") &&
			!strings.Contains(ctx.Req.Header.Get(acceptEncodingHeader), "x-gzip") {
			return
		}

		// If the client is asking for a specific range of bytes - don't compress
		if rangeHdr := ctx.Req.Header.Get(rangeHeader); rangeHdr != "" {

			match := regex.FindStringSubmatch(rangeHdr)
			if len(match) > 1 {
				return
			}
		}

		// OK we should proxy the response writer
		// We are still not necessarily going to compress...
		proxyWriter := &ProxyResponseWriter{
			ResponseWriter: ctx.Resp,
		}
		defer proxyWriter.Close()

		ctx.Resp = proxyWriter
		ctx.MapTo(proxyWriter, (*http.ResponseWriter)(nil))

		// Check if render middleware has been registered,
		// if yes, we need to modify ResponseWriter for it as well.
		if _, ok := ctx.Render.(*macaron.DummyRender); !ok {
			ctx.Render.SetResponseWriter(proxyWriter)
		}

		ctx.Next()
	}
}

// ProxyResponseWriter is a wrapped macaron ResponseWriter that may compress its contents
type ProxyResponseWriter struct {
	writer io.WriteCloser
	macaron.ResponseWriter
	stopped bool

	code int
	buf  []byte
}

// Write appends data to the proxied gzip writer.
func (proxy *ProxyResponseWriter) Write(b []byte) (int, error) {
	// if writer is initialized, use the writer
	if proxy.writer != nil {
		return proxy.writer.Write(b)
	}

	proxy.buf = append(proxy.buf, b...)

	var (
		contentLength, _ = strconv.Atoi(proxy.Header().Get(contentLengthHeader))
		contentType      = proxy.Header().Get(contentTypeHeader)
		contentEncoding  = proxy.Header().Get(contentEncodingHeader)
	)

	// OK if an encoding hasn't been chosen, and content length > 1400
	// and content type isn't a compressed type
	if contentEncoding == "" &&
		(contentLength == 0 || contentLength >= MinSize) &&
		(contentType == "" || !compressedContentType(contentType)) {
		// If current buffer is less than the min size and a Content-Length isn't set, then wait
		if len(proxy.buf) < MinSize && contentLength == 0 {
			return len(b), nil
		}

		// If the Content-Length is larger than minSize or the current buffer is larger than minSize, then continue.
		if contentLength >= MinSize || len(proxy.buf) >= MinSize {
			// if we don't know the content type, infer it
			if contentType == "" {
				contentType = http.DetectContentType(proxy.buf)
				proxy.Header().Set(contentTypeHeader, contentType)
			}
			// If the Content-Type is not compressed - Compress!
			if !compressedContentType(contentType) {
				if err := proxy.startGzip(); err != nil {
					return 0, err
				}
				return len(b), nil
			}
		}
	}
	// If we got here, we should not GZIP this response.
	if err := proxy.startPlain(); err != nil {
		return 0, err
	}
	return len(b), nil
}

func (proxy *ProxyResponseWriter) startGzip() error {
	// Set the content-encoding and vary headers.
	proxy.Header().Set(contentEncodingHeader, "gzip")
	proxy.Header().Set(varyHeader, acceptEncodingHeader)

	// if the Content-Length is already set, then calls to Write on gzip
	// will fail to set the Content-Length header since its already set
	// See: https://github.com/golang/go/issues/14975.
	proxy.Header().Del(contentLengthHeader)

	// Write the header to gzip response.
	if proxy.code != 0 {
		proxy.ResponseWriter.WriteHeader(proxy.code)
		// Ensure that no other WriteHeader's happen
		proxy.code = 0
	}

	// Initialize and flush the buffer into the gzip response if there are any bytes.
	// If there aren't any, we shouldn't initialize it yet because on Close it will
	// write the gzip header even if nothing was ever written.
	if len(proxy.buf) > 0 {
		// Initialize the GZIP response.
		proxy.writer = writerPool.Get(proxy.ResponseWriter)

		return proxy.writeBuf()
	}
	return nil
}

func (proxy *ProxyResponseWriter) startPlain() error {
	if proxy.code != 0 {
		proxy.ResponseWriter.WriteHeader(proxy.code)
		proxy.code = 0
	}
	proxy.stopped = true
	proxy.writer = noopCloser{proxy.ResponseWriter}
	return proxy.writeBuf()
}

func (proxy *ProxyResponseWriter) writeBuf() error {
	if proxy.buf == nil {
		return nil
	}

	n, err := proxy.writer.Write(proxy.buf)

	// This should never happen (per io.Writer docs), but if the write didn't
	// accept the entire buffer but returned no specific error, we have no clue
	// what's going on, so abort just to be safe.
	if err == nil && n < len(proxy.buf) {
		err = io.ErrShortWrite
	}
	proxy.buf = nil
	return err
}

// WriteHeader will ensure that we have setup the writer before we write the header
func (proxy *ProxyResponseWriter) WriteHeader(code int) {
	if proxy.code == 0 {
		proxy.code = code
	}
}

// Close the writer
func (proxy *ProxyResponseWriter) Close() error {
	if proxy.stopped {
		return nil
	}

	if proxy.writer == nil {
		err := proxy.startPlain()
		if err != nil {
			return fmt.Errorf("GzipMiddleware: write to regular responseWriter at close gets error: %q", err.Error())
		}
	}

	err := proxy.writer.Close()

	if poolWriter, ok := proxy.writer.(*gzip.Writer); ok {
		writerPool.Put(poolWriter)
	}

	proxy.writer = nil
	proxy.stopped = true
	return err
}

// Flush the writer
func (proxy *ProxyResponseWriter) Flush() {
	if proxy.writer == nil {
		return
	}

	if gw, ok := proxy.writer.(*gzip.Writer); ok {
		gw.Flush()
	}

	proxy.ResponseWriter.Flush()
}

// Hijack implements http.Hijacker. If the underlying ResponseWriter is a
// Hijacker, its Hijack method is returned. Otherwise an error is returned.
func (proxy *ProxyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hijacker, ok := proxy.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
	}
	return hijacker.Hijack()
}

// verify Hijacker interface implementation
var _ http.Hijacker = &ProxyResponseWriter{}

func compressedContentType(contentType string) bool {
	switch contentType {
	case "application/zip":
		return true
	case "application/x-gzip":
		return true
	case "application/gzip":
		return true
	default:
		return false
	}
}
<commit_msg>Ensure Written is set in GZIP ProxyResponseWriter (#9018)<commit_after>// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package gzip

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"gitea.com/macaron/macaron"
	"github.com/klauspost/compress/gzip"
)

const (
	acceptEncodingHeader  = "Accept-Encoding"
	contentEncodingHeader = "Content-Encoding"
	contentLengthHeader   = "Content-Length"
	contentTypeHeader     = "Content-Type"
	rangeHeader           = "Range"
	varyHeader            = "Vary"
)

const (
	// MinSize is the minimum size of content we will compress
	MinSize = 1400
)

// noopClosers are io.Writers with a shim to prevent early closure
type noopCloser struct {
	io.Writer
}

func (noopCloser) Close() error { return nil }

// WriterPool is a gzip writer pool to reduce workload on creation of
// gzip writers
type WriterPool struct {
	pool             sync.Pool
	compressionLevel int
}

// NewWriterPool creates a new pool
func NewWriterPool(compressionLevel int) *WriterPool {
	return &WriterPool{pool: sync.Pool{
		// New will return nil, we'll manage the creation of new
		// writers in the middleware
		New: func() interface{} { return nil },
	},
		compressionLevel: compressionLevel}
}

// Get a writer from the pool - or create one if not available
func (wp *WriterPool) Get(rw macaron.ResponseWriter) *gzip.Writer {
	ret := wp.pool.Get()
	if ret == nil {
		ret, _ = gzip.NewWriterLevel(rw, wp.compressionLevel)
	} else {
		ret.(*gzip.Writer).Reset(rw)
	}
	return ret.(*gzip.Writer)
}

// Put returns a writer to the pool
func (wp *WriterPool) Put(w *gzip.Writer) {
	wp.pool.Put(w)
}

var writerPool WriterPool

// Options represents the configuration for the gzip middleware
type Options struct {
	CompressionLevel int
}

func validateCompressionLevel(level int) bool {
	return level == gzip.DefaultCompression ||
		level == gzip.ConstantCompression ||
		(level >= gzip.BestSpeed && level <= gzip.BestCompression)
}

func validate(options []Options) Options {
	// Default to level 4 compression (Best results seem to be between 4 and 6)
	opt := Options{CompressionLevel: 4}
	if len(options) > 0 {
		opt = options[0]
	}
	if !validateCompressionLevel(opt.CompressionLevel) {
		opt.CompressionLevel = 4
	}
	return opt
}

// Middleware creates a macaron.Handler to proxy the response
func Middleware(options ...Options) macaron.Handler {
	opt := validate(options)
	writerPool = *NewWriterPool(opt.CompressionLevel)
	regex := regexp.MustCompile(`bytes=(\d+)\-.*`)

	return func(ctx *macaron.Context) {
		// If the client won't accept gzip or x-gzip don't compress
		if !strings.Contains(ctx.Req.Header.Get(acceptEncodingHeader), "gzip") &&
			!strings.Contains(ctx.Req.Header.Get(acceptEncodingHeader), "x-gzip") {
			return
		}

		// If the client is asking for a specific range of bytes - don't compress
		if rangeHdr := ctx.Req.Header.Get(rangeHeader); rangeHdr != "" {

			match := regex.FindStringSubmatch(rangeHdr)
			if len(match) > 1 {
				return
			}
		}

		// OK we should proxy the response writer
		// We are still not necessarily going to compress...
		proxyWriter := &ProxyResponseWriter{
			internal: ctx.Resp,
		}
		defer proxyWriter.Close()

		ctx.Resp = proxyWriter
		ctx.MapTo(proxyWriter, (*http.ResponseWriter)(nil))

		// Check if render middleware has been registered,
		// if yes, we need to modify ResponseWriter for it as well.
		if _, ok := ctx.Render.(*macaron.DummyRender); !ok {
			ctx.Render.SetResponseWriter(proxyWriter)
		}

		ctx.Next()
		ctx.Resp = proxyWriter.internal
	}
}

// ProxyResponseWriter is a wrapped macaron ResponseWriter that may compress its contents
type ProxyResponseWriter struct {
	writer   io.WriteCloser
	internal macaron.ResponseWriter
	stopped  bool

	code int
	buf  []byte
}

// Header returns the header map
func (proxy *ProxyResponseWriter) Header() http.Header {
	return proxy.internal.Header()
}

// Status returns the status code of the response or 0 if the response has not been written.
func (proxy *ProxyResponseWriter) Status() int {
	if proxy.code != 0 {
		return proxy.code
	}
	return proxy.internal.Status()
}

// Written returns whether or not the ResponseWriter has been written.
func (proxy *ProxyResponseWriter) Written() bool {
	if proxy.code != 0 {
		return true
	}
	return proxy.internal.Written()
}

// Size returns the size of the response body.
func (proxy *ProxyResponseWriter) Size() int {
	return proxy.internal.Size()
}

// Before allows for a function to be called before the ResponseWriter has been written to. This is
// useful for setting headers or any other operations that must happen before a response has been written.
func (proxy *ProxyResponseWriter) Before(before macaron.BeforeFunc) {
	proxy.internal.Before(before)
}

// Write appends data to the proxied gzip writer.
func (proxy *ProxyResponseWriter) Write(b []byte) (int, error) {
	// if writer is initialized, use the writer
	if proxy.writer != nil {
		return proxy.writer.Write(b)
	}

	proxy.buf = append(proxy.buf, b...)

	var (
		contentLength, _ = strconv.Atoi(proxy.Header().Get(contentLengthHeader))
		contentType      = proxy.Header().Get(contentTypeHeader)
		contentEncoding  = proxy.Header().Get(contentEncodingHeader)
	)

	// OK if an encoding hasn't been chosen, and content length > 1400
	// and content type isn't a compressed type
	if contentEncoding == "" &&
		(contentLength == 0 || contentLength >= MinSize) &&
		(contentType == "" || !compressedContentType(contentType)) {
		// If current buffer is less than the min size and a Content-Length isn't set, then wait
		if len(proxy.buf) < MinSize && contentLength == 0 {
			return len(b), nil
		}

		// If the Content-Length is larger than minSize or the current buffer is larger than minSize, then continue.
		if contentLength >= MinSize || len(proxy.buf) >= MinSize {
			// if we don't know the content type, infer it
			if contentType == "" {
				contentType = http.DetectContentType(proxy.buf)
				proxy.Header().Set(contentTypeHeader, contentType)
			}
			// If the Content-Type is not compressed - Compress!
			if !compressedContentType(contentType) {
				if err := proxy.startGzip(); err != nil {
					return 0, err
				}
				return len(b), nil
			}
		}
	}
	// If we got here, we should not GZIP this response.
	if err := proxy.startPlain(); err != nil {
		return 0, err
	}
	return len(b), nil
}

func (proxy *ProxyResponseWriter) startGzip() error {
	// Set the content-encoding and vary headers.
	proxy.Header().Set(contentEncodingHeader, "gzip")
	proxy.Header().Set(varyHeader, acceptEncodingHeader)

	// if the Content-Length is already set, then calls to Write on gzip
	// will fail to set the Content-Length header since its already set
	// See: https://github.com/golang/go/issues/14975.
	proxy.Header().Del(contentLengthHeader)

	// Write the header to gzip response.
	if proxy.code != 0 {
		proxy.internal.WriteHeader(proxy.code)
		// Ensure that no other WriteHeader's happen
		proxy.code = 0
	}

	// Initialize and flush the buffer into the gzip response if there are any bytes.
	// If there aren't any, we shouldn't initialize it yet because on Close it will
	// write the gzip header even if nothing was ever written.
	if len(proxy.buf) > 0 {
		// Initialize the GZIP response.
		proxy.writer = writerPool.Get(proxy.internal)

		return proxy.writeBuf()
	}
	return nil
}

func (proxy *ProxyResponseWriter) startPlain() error {
	if proxy.code != 0 {
		proxy.internal.WriteHeader(proxy.code)
		proxy.code = 0
	}
	proxy.stopped = true
	proxy.writer = noopCloser{proxy.internal}
	return proxy.writeBuf()
}

func (proxy *ProxyResponseWriter) writeBuf() error {
	if proxy.buf == nil {
		return nil
	}

	n, err := proxy.writer.Write(proxy.buf)

	// This should never happen (per io.Writer docs), but if the write didn't
	// accept the entire buffer but returned no specific error, we have no clue
	// what's going on, so abort just to be safe.
	if err == nil && n < len(proxy.buf) {
		err = io.ErrShortWrite
	}
	proxy.buf = nil
	return err
}

// WriteHeader will ensure that we have setup the writer before we write the header
func (proxy *ProxyResponseWriter) WriteHeader(code int) {
	if proxy.code == 0 {
		proxy.code = code
	}
}

// Close the writer
func (proxy *ProxyResponseWriter) Close() error {
	if proxy.stopped {
		return nil
	}

	if proxy.writer == nil {
		err := proxy.startPlain()
		if err != nil {
			return fmt.Errorf("GzipMiddleware: write to regular responseWriter at close gets error: %q", err.Error())
		}
	}

	err := proxy.writer.Close()

	if poolWriter, ok := proxy.writer.(*gzip.Writer); ok {
		writerPool.Put(poolWriter)
	}

	proxy.writer = nil
	proxy.stopped = true
	return err
}

// Flush the writer
func (proxy *ProxyResponseWriter) Flush() {
	if proxy.writer == nil {
		return
	}

	if gw, ok := proxy.writer.(*gzip.Writer); ok {
		gw.Flush()
	}

	proxy.internal.Flush()
}

// Hijack implements http.Hijacker. If the underlying ResponseWriter is a
// Hijacker, its Hijack method is returned. Otherwise an error is returned.
func (proxy *ProxyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	hijacker, ok := proxy.internal.(http.Hijacker)
	if !ok {
		return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
	}
	return hijacker.Hijack()
}

// verify Hijacker interface implementation
var _ http.Hijacker = &ProxyResponseWriter{}

func compressedContentType(contentType string) bool {
	switch contentType {
	case "application/zip":
		return true
	case "application/x-gzip":
		return true
	case "application/gzip":
		return true
	default:
		return false
	}
}
<|endoftext|>


<commit_before>package analytics

import (
	"time"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/search"
)

// ABTestTaskRes represents a generic task submitted to the Analytics API for an
// AB test.
type ABTestTaskRes struct {
	ABTestID int    `json:"abTestID"`
	Index    string `json:"index"`
	TaskID   int64  `json:"taskID"`
	wait     func(index string, taskID int64) error
}

// Wait blocks until the AB test task completes or if there is an error while
// waiting for its completion.
func (r ABTestTaskRes) Wait() error {
	return r.wait(r.Index, r.TaskID)
}

// GetABTestsRes represents a response payload from the Analytics API when
// performing a GetABTests call.
type GetABTestsRes struct {
	ABTests []ABTestResponse `json:"abtests"`
	Count   int              `json:"count"`
	Total   int              `json:"total"`
}

// ABTestResponse represents an AB test as returned by the Analytics API when
// retrieved as part of a response.
type ABTestResponse struct {
	ABTestID               int               `json:"abTestID"`
	ClickSignificance      int               `json:"clickSignificance"`
	ConversionSignificance float64           `json:"conversionSignificance"`
	CreatedAt              time.Time         `json:"createdAt"`
	EndAt                  time.Time         `json:"endAt"`
	Name                   string            `json:"name"`
	Status                 string            `json:"status"`
	Variants               []VariantResponse `json:"variants"`
}

// VariantResponse represents an AB test's variant as returned by the Analytics
// API when retrieved as part of a response.
type VariantResponse struct {
	AverageClickPosition   int                 `json:"averageClickPosition"`
	ClickCount             int                 `json:"clickCount"`
	ClickThroughRate       float64             `json:"clickThroughRate"`
	ConversionCount        int                 `json:"conversionCount"`
	ConversionRate         float64             `json:"conversionRate"`
	Description            string              `json:"description"`
	Index                  string              `json:"index"`
	NoResultCount          int                 `json:"noResultCount"`
	SearchCount            int                 `json:"searchCount"`
	TrackedSearchCount     int                 `json:"trackedSearchCount"`
	TrafficPercentage      int                 `json:"trafficPercentage"`
	UserCount              int                 `json:"userCount"`
	CustomSearchParameters *search.QueryParams `json:"customSearchParameters"`
}
<commit_msg>fix(analytics): correct AB testing average click pos type<commit_after>package analytics

import (
	"time"

	"github.com/algolia/algoliasearch-client-go/v3/algolia/search"
)

// ABTestTaskRes represents a generic task submitted to the Analytics API for an
// AB test.
type ABTestTaskRes struct {
	ABTestID int    `json:"abTestID"`
	Index    string `json:"index"`
	TaskID   int64  `json:"taskID"`
	wait     func(index string, taskID int64) error
}

// Wait blocks until the AB test task completes or if there is an error while
// waiting for its completion.
func (r ABTestTaskRes) Wait() error {
	return r.wait(r.Index, r.TaskID)
}

// GetABTestsRes represents a response payload from the Analytics API when
// performing a GetABTests call.
type GetABTestsRes struct {
	ABTests []ABTestResponse `json:"abtests"`
	Count   int              `json:"count"`
	Total   int              `json:"total"`
}

// ABTestResponse represents an AB test as returned by the Analytics API when
// retrieved as part of a response.
type ABTestResponse struct {
	ABTestID               int               `json:"abTestID"`
	ClickSignificance      int               `json:"clickSignificance"`
	ConversionSignificance float64           `json:"conversionSignificance"`
	CreatedAt              time.Time         `json:"createdAt"`
	EndAt                  time.Time         `json:"endAt"`
	Name                   string            `json:"name"`
	Status                 string            `json:"status"`
	Variants               []VariantResponse `json:"variants"`
}

// VariantResponse represents an AB test's variant as returned by the Analytics
// API when retrieved as part of a response.
type VariantResponse struct {
	AverageClickPosition   float64             `json:"averageClickPosition"`
	ClickCount             int                 `json:"clickCount"`
	ClickThroughRate       float64             `json:"clickThroughRate"`
	ConversionCount        int                 `json:"conversionCount"`
	ConversionRate         float64             `json:"conversionRate"`
	Description            string              `json:"description"`
	Index                  string              `json:"index"`
	NoResultCount          int                 `json:"noResultCount"`
	SearchCount            int                 `json:"searchCount"`
	TrackedSearchCount     int                 `json:"trackedSearchCount"`
	TrafficPercentage      int                 `json:"trafficPercentage"`
	UserCount              int                 `json:"userCount"`
	CustomSearchParameters *search.QueryParams `json:"customSearchParameters"`
}
<|endoftext|>


<commit_before>package lang

import (
    //"fmt"
    "time"
    "unsafe"
    . "jvmgo/any"
    "jvmgo/jvm/rtda"
    rtc "jvmgo/jvm/rtda/class"
)

func init() {
    _system(arraycopy,          "arraycopy",            "(Ljava/lang/Object;ILjava/lang/Object;II)V")
    _system(currentTimeMillis,  "currentTimeMillis",    "()J")
    _system(identityHashCode,   "identityHashCode",     "(Ljava/lang/Object;)I")
    _system(initProperties,     "initProperties",       "(Ljava/util/Properties;)Ljava/util/Properties;")
    _system(nanoTime,           "nanoTime",             "()J")
    _system(setErr0,            "setErr0",              "(Ljava/io/PrintStream;)V")
    _system(setIn0,             "setIn0",               "(Ljava/io/InputStream;)V")
    _system(setOut0,            "setOut0",              "(Ljava/io/PrintStream;)V")
}

func _system(method Any, name, desc string) {
    rtc.RegisterNativeMethod("java/lang/System", name, desc, method)
}

// public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
// (Ljava/lang/Object;ILjava/lang/Object;II)V
func arraycopy(frame *rtda.Frame) {
    vars := frame.LocalVars()
    src := vars.GetRef(0)
    srcPos := vars.GetInt(1)
    dest := vars.GetRef(2)
    destPos := vars.GetInt(3)
    length := vars.GetInt(4)

    // NullPointerException
    if src == nil || dest == nil {
        panic("NPE") // todo
    }
    // ArrayStoreException
    if !rtc.HaveSameArrayType(src, dest) {
        panic("ArrayStoreException")
    }
    // IndexOutOfBoundsException
    if srcPos < 0 || destPos < 0 || length < 0 ||
            srcPos + length > rtc.ArrayLength(src) ||
            destPos + length > rtc.ArrayLength(dest) {

        panic("IndexOutOfBoundsException") // todo
    }

    rtc.ArrayCopy(src, dest, srcPos, destPos, length)
}

// public static native long currentTimeMillis();
// ()J
func currentTimeMillis(frame *rtda.Frame) {
    millis := time.Now().UnixNano() / 1000
    stack := frame.OperandStack()
    stack.PushLong(millis)
}

// public static native int identityHashCode(Object x);
// (Ljava/lang/Object;)I
func identityHashCode(frame *rtda.Frame) {
    vars := frame.LocalVars()
    ref := vars.GetRef(0)

    // todo
    hashCode := int32(uintptr(unsafe.Pointer(ref)))
    stack := frame.OperandStack()
    stack.PushInt(hashCode)
}

// private static native Properties initProperties(Properties props);
// (Ljava/util/Properties;)Ljava/util/Properties;
func initProperties(frame *rtda.Frame) {
    vars := frame.LocalVars()
    props := vars.GetRef(0)

    stack := frame.OperandStack()
    stack.PushRef(props)
    // public synchronized Object setProperty(String key, String value)
    setPropMethod := props.Class().GetInstanceMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")
    thread := frame.Thread()
    for key, val := range _props() {
        jKey := rtda.NewJString(key, frame)
        jVal := rtda.NewJString(val, frame)
        vars := thread.InvokeMethod2(setPropMethod)
        vars.SetRef(0, props) // this
        vars.SetRef(1, jKey)
        vars.SetRef(2, jVal)
    }
}

func _props() map[string]string {
    return map[string]string{
        "file.encoding": "UTF-8",
        "sun.stdout.encoding": "UTF-8",
        "sun.stderr.encoding": "UTF-8",
        "file.separator": "/", // todo os.PathSeparator
        "path.separator": ":", // todo os.PathListSeparator
        "line.separator": "\n",
    }
}

// public static native long nanoTime();
// ()J
func nanoTime(frame *rtda.Frame) {
    nanoTime := time.Now().UnixNano()
    stack := frame.OperandStack()
    stack.PushLong(nanoTime)
}

// private static native void setErr0(PrintStream err);
// (Ljava/io/PrintStream;)V
func setErr0(frame *rtda.Frame) {
    vars := frame.LocalVars()
    err := vars.GetRef(0)

    sysClass := frame.Method().Class()
    sysClass.SetStaticValue("err", "Ljava/io/PrintStream;", err)
}

// private static native void setIn0(InputStream in);
// (Ljava/io/InputStream;)V
func setIn0(frame *rtda.Frame) {
    vars := frame.LocalVars()
    in := vars.GetRef(0)

    sysClass := frame.Method().Class()
    sysClass.SetStaticValue("in", "Ljava/io/InputStream;", in)
}

// private static native void setOut0(PrintStream out);
// (Ljava/io/PrintStream;)V
func setOut0(frame *rtda.Frame) {
    vars := frame.LocalVars()
    out := vars.GetRef(0)

    sysClass := frame.Method().Class()
    sysClass.SetStaticValue("out", "Ljava/io/PrintStream;", out)
}
<commit_msg>using InvokeMethodWithShim()<commit_after>package lang

import (
    //"fmt"
    "time"
    "unsafe"
    . "jvmgo/any"
    "jvmgo/jvm/rtda"
    rtc "jvmgo/jvm/rtda/class"
)

func init() {
    _system(arraycopy,          "arraycopy",            "(Ljava/lang/Object;ILjava/lang/Object;II)V")
    _system(currentTimeMillis,  "currentTimeMillis",    "()J")
    _system(identityHashCode,   "identityHashCode",     "(Ljava/lang/Object;)I")
    _system(initProperties,     "initProperties",       "(Ljava/util/Properties;)Ljava/util/Properties;")
    _system(nanoTime,           "nanoTime",             "()J")
    _system(setErr0,            "setErr0",              "(Ljava/io/PrintStream;)V")
    _system(setIn0,             "setIn0",               "(Ljava/io/InputStream;)V")
    _system(setOut0,            "setOut0",              "(Ljava/io/PrintStream;)V")
}

func _system(method Any, name, desc string) {
    rtc.RegisterNativeMethod("java/lang/System", name, desc, method)
}

// public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
// (Ljava/lang/Object;ILjava/lang/Object;II)V
func arraycopy(frame *rtda.Frame) {
    vars := frame.LocalVars()
    src := vars.GetRef(0)
    srcPos := vars.GetInt(1)
    dest := vars.GetRef(2)
    destPos := vars.GetInt(3)
    length := vars.GetInt(4)

    // NullPointerException
    if src == nil || dest == nil {
        panic("NPE") // todo
    }
    // ArrayStoreException
    if !rtc.HaveSameArrayType(src, dest) {
        panic("ArrayStoreException")
    }
    // IndexOutOfBoundsException
    if srcPos < 0 || destPos < 0 || length < 0 ||
            srcPos + length > rtc.ArrayLength(src) ||
            destPos + length > rtc.ArrayLength(dest) {

        panic("IndexOutOfBoundsException") // todo
    }

    rtc.ArrayCopy(src, dest, srcPos, destPos, length)
}

// public static native long currentTimeMillis();
// ()J
func currentTimeMillis(frame *rtda.Frame) {
    millis := time.Now().UnixNano() / 1000
    stack := frame.OperandStack()
    stack.PushLong(millis)
}

// public static native int identityHashCode(Object x);
// (Ljava/lang/Object;)I
func identityHashCode(frame *rtda.Frame) {
    vars := frame.LocalVars()
    ref := vars.GetRef(0)

    // todo
    hashCode := int32(uintptr(unsafe.Pointer(ref)))
    stack := frame.OperandStack()
    stack.PushInt(hashCode)
}

// private static native Properties initProperties(Properties props);
// (Ljava/util/Properties;)Ljava/util/Properties;
func initProperties(frame *rtda.Frame) {
    vars := frame.LocalVars()
    props := vars.GetRef(0)

    stack := frame.OperandStack()
    stack.PushRef(props)
    // public synchronized Object setProperty(String key, String value)
    setPropMethod := props.Class().GetInstanceMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")
    thread := frame.Thread()
    for key, val := range _props() {
        jKey := rtda.NewJString(key, frame)
        jVal := rtda.NewJString(val, frame)
        args := []Any{props, jKey, jVal}
        thread.InvokeMethodWithShim(setPropMethod, args)
    }
}

func _props() map[string]string {
    return map[string]string{
        "file.encoding": "UTF-8",
        "sun.stdout.encoding": "UTF-8",
        "sun.stderr.encoding": "UTF-8",
        "file.separator": "/", // todo os.PathSeparator
        "path.separator": ":", // todo os.PathListSeparator
        "line.separator": "\n",
    }
}

// public static native long nanoTime();
// ()J
func nanoTime(frame *rtda.Frame) {
    nanoTime := time.Now().UnixNano()
    stack := frame.OperandStack()
    stack.PushLong(nanoTime)
}

// private static native void setErr0(PrintStream err);
// (Ljava/io/PrintStream;)V
func setErr0(frame *rtda.Frame) {
    vars := frame.LocalVars()
    err := vars.GetRef(0)

    sysClass := frame.Method().Class()
    sysClass.SetStaticValue("err", "Ljava/io/PrintStream;", err)
}

// private static native void setIn0(InputStream in);
// (Ljava/io/InputStream;)V
func setIn0(frame *rtda.Frame) {
    vars := frame.LocalVars()
    in := vars.GetRef(0)

    sysClass := frame.Method().Class()
    sysClass.SetStaticValue("in", "Ljava/io/InputStream;", in)
}

// private static native void setOut0(PrintStream out);
// (Ljava/io/PrintStream;)V
func setOut0(frame *rtda.Frame) {
    vars := frame.LocalVars()
    out := vars.GetRef(0)

    sysClass := frame.Method().Class()
    sysClass.SetStaticValue("out", "Ljava/io/PrintStream;", out)
}
<|endoftext|>


<commit_before>package api_v1

import (
	"scal"
	"scal/event_lib"
	"scal/settings"
	"scal/storage"
	"scal/user_lib"
	. "scal/user_lib"
	"time"

	. "github.com/ilius/ripo"
)

func init() {
	routeGroups = append(routeGroups, RouteGroup{
		Base: "me",
		Map: RouteMap{
			"SetUserFullName": {
				Method:  "PUT",
				Pattern: "full-name",
				Handler: SetUserFullName,
			},
			"UnsetUserFullName": {
				Method:  "DELETE",
				Pattern: "full-name",
				Handler: UnsetUserFullName,
			},
			"GetUserInfo": {
				Method:  "GET",
				Pattern: "info",
				Handler: GetUserInfo,
			},
			"GetUserLoginHistory": {
				Method:  "GET",
				Pattern: "login-history",
				Handler: GetUserLoginHistory,
			},
			"SetUserDefaultGroupId": {
				Method:  "PUT",
				Pattern: "default-group",
				Handler: SetUserDefaultGroupId,
			},
			"UnsetUserDefaultGroupId": {
				Method:  "DELETE",
				Pattern: "default-group",
				Handler: UnsetUserDefaultGroupId,
			},
			"GetUserDataFull": {
				Method:  "GET",
				Pattern: "data-full",
				Handler: GetUserDataFull,
			},
		},
	})
}

func SetUserFullName(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	const attrName = "fullName"
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	attrValue, err := req.GetString(attrName)
	if err != nil {
		return nil, err
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "SetUserFullName",
		FullName: &[2]*string{
			&userModel.FullName,
			attrValue,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.FullName = *attrValue
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func UnsetUserFullName(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "UnsetUserFullName",
		FullName: &[2]*string{
			&userModel.FullName,
			nil,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.FullName = ""
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func SetUserDefaultGroupId(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	const attrName = "defaultGroupId"
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	attrValue, err := req.GetString(attrName)
	if err != nil {
		return nil, err
	}

	groupModel, err := event_lib.LoadGroupModelByIdHex(
		"defaultGroupId",
		db,
		*attrValue,
	)
	if err != nil {
		return nil, err
	}
	groupId := groupModel.Id

	if groupModel.OwnerEmail != email {
		return nil, NewError(InvalidArgument, "invalid 'defaultGroupId'", nil)
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "SetUserDefaultGroupId",
		DefaultGroupId: &[2]*string{
			userModel.DefaultGroupId,
			&groupId,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.DefaultGroupId = &groupId
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func UnsetUserDefaultGroupId(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "UnsetUserDefaultGroupId",
		DefaultGroupId: &[2]*string{
			userModel.DefaultGroupId,
			nil,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.DefaultGroupId = nil
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func loadLastLogins(req Request, userModel *user_lib.UserModel, limit int) ([]*user_lib.UserLoginAttemptModel, error) {
	result := []*user_lib.UserLoginAttemptModel{}
	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}
	cond := db.NewCondition(storage.AND)
	cond.IdEquals("userId", userModel.Id)
	err = db.FindAll(&result, &storage.FindInput{
		Collection: storage.C_userLogins,
		Condition:  cond,
		SortBy:     "-time",
		Limit:      limit,
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}
	return result, nil
}

func GetUserInfo(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	// -----------------------------------------------
	lastLogins, err := loadLastLogins(req, userModel, settings.USER_INFO_LAST_LOGINS_LIMIT)
	if err != nil {
		return nil, err
	}
	return &Response{
		Data: scal.M{
			"email":          email,
			"fullName":       userModel.FullName,
			"defaultGroupId": *userModel.DefaultGroupId,
			"emailConfirmed": userModel.EmailConfirmed,
			//"locked": userModel.Locked,

			"lastLogoutTime": userModel.LastLogoutTime,

			"lastLogins": lastLogins,
		},
	}, nil
}

func GetUserLoginHistory(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	// -----------------------------------------------
	const defaultLimit = settings.USER_LOGIN_HISTORY_DEFAULT_LIMIT
	limit, err := req.GetIntDefault("limit", defaultLimit)
	if err != nil {
		return nil, err
	}
	if limit <= 0 {
		limit = defaultLimit
		// otherwise db will return everything
	}
	lastLogins, err := loadLastLogins(req, userModel, limit)
	if err != nil {
		return nil, err
	}
	return &Response{
		Data: scal.M{
			"lastLogins": lastLogins,
		},
	}, nil
}

func GetUserDataFull(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	// -----------------------------------------------
	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	lastLogins, err := loadLastLogins(req, userModel, settings.USER_INFO_LAST_LOGINS_LIMIT)
	if err != nil {
		return nil, err
	}

	userData := scal.M{
		"email":          email,
		"fullName":       userModel.FullName,
		"defaultGroupId": userModel.DefaultGroupId,
		"emailConfirmed": userModel.EmailConfirmed,
		//"locked": userModel.Locked,
		"lastLogoutTime": userModel.LastLogoutTime,
		"lastLogins":     lastLogins,
	}

	groupCond := db.NewCondition(storage.OR).
		Equals("ownerEmail", email).
		Includes("readAccessEmails", email)

	var groups []event_lib.ListGroupsRow
	err = db.FindAll(&groups, &storage.FindInput{
		Collection: storage.C_group,
		Condition:  groupCond,
		SortBy:     "_id",
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}
	if groups == nil {
		groups = make([]event_lib.ListGroupsRow, 0)
	}

	events, err := getMyEventsFullData(db, email, nil)
	if err != nil {
		return nil, err
	}

	return &Response{
		Data: scal.M{
			"user":   userData,
			"groups": groups,
			"events": events,
		},
	}, nil
}
<commit_msg>user_handlers.go: change order or routes to match the functions<commit_after>package api_v1

import (
	"scal"
	"scal/event_lib"
	"scal/settings"
	"scal/storage"
	"scal/user_lib"
	. "scal/user_lib"
	"time"

	. "github.com/ilius/ripo"
)

func init() {
	routeGroups = append(routeGroups, RouteGroup{
		Base: "me",
		Map: RouteMap{
			"SetUserFullName": {
				Method:  "PUT",
				Pattern: "full-name",
				Handler: SetUserFullName,
			},
			"UnsetUserFullName": {
				Method:  "DELETE",
				Pattern: "full-name",
				Handler: UnsetUserFullName,
			},
			"SetUserDefaultGroupId": {
				Method:  "PUT",
				Pattern: "default-group",
				Handler: SetUserDefaultGroupId,
			},
			"UnsetUserDefaultGroupId": {
				Method:  "DELETE",
				Pattern: "default-group",
				Handler: UnsetUserDefaultGroupId,
			},
			"GetUserInfo": {
				Method:  "GET",
				Pattern: "info",
				Handler: GetUserInfo,
			},
			"GetUserLoginHistory": {
				Method:  "GET",
				Pattern: "login-history",
				Handler: GetUserLoginHistory,
			},
			"GetUserDataFull": {
				Method:  "GET",
				Pattern: "data-full",
				Handler: GetUserDataFull,
			},
		},
	})
}

func SetUserFullName(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	const attrName = "fullName"
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	attrValue, err := req.GetString(attrName)
	if err != nil {
		return nil, err
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "SetUserFullName",
		FullName: &[2]*string{
			&userModel.FullName,
			attrValue,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.FullName = *attrValue
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func UnsetUserFullName(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "UnsetUserFullName",
		FullName: &[2]*string{
			&userModel.FullName,
			nil,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.FullName = ""
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func SetUserDefaultGroupId(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	const attrName = "defaultGroupId"
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	attrValue, err := req.GetString(attrName)
	if err != nil {
		return nil, err
	}

	groupModel, err := event_lib.LoadGroupModelByIdHex(
		"defaultGroupId",
		db,
		*attrValue,
	)
	if err != nil {
		return nil, err
	}
	groupId := groupModel.Id

	if groupModel.OwnerEmail != email {
		return nil, NewError(InvalidArgument, "invalid 'defaultGroupId'", nil)
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "SetUserDefaultGroupId",
		DefaultGroupId: &[2]*string{
			userModel.DefaultGroupId,
			&groupId,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.DefaultGroupId = &groupId
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func UnsetUserDefaultGroupId(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	failed, unlock := resLock.User(email)
	if failed {
		return nil, NewError(ResourceLocked, "user is being modified by another request", nil)
	}
	defer unlock()
	// -----------------------------------------------
	remoteIp, err := req.RemoteIP()
	if err != nil {
		return nil, err
	}

	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	err = db.Insert(UserChangeLogModel{
		Time:          time.Now(),
		RequestEmail:  email,
		RemoteIp:      remoteIp,
		TokenIssuedAt: *userModel.TokenIssuedAt,
		FuncName:      "UnsetUserDefaultGroupId",
		DefaultGroupId: &[2]*string{
			userModel.DefaultGroupId,
			nil,
		},
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	userModel.DefaultGroupId = nil
	err = db.Update(userModel)
	if err != nil {
		return nil, NewError(Internal, "", err)
	}

	return &Response{}, nil
}

func loadLastLogins(req Request, userModel *user_lib.UserModel, limit int) ([]*user_lib.UserLoginAttemptModel, error) {
	result := []*user_lib.UserLoginAttemptModel{}
	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}
	cond := db.NewCondition(storage.AND)
	cond.IdEquals("userId", userModel.Id)
	err = db.FindAll(&result, &storage.FindInput{
		Collection: storage.C_userLogins,
		Condition:  cond,
		SortBy:     "-time",
		Limit:      limit,
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}
	return result, nil
}

func GetUserInfo(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	// -----------------------------------------------
	lastLogins, err := loadLastLogins(req, userModel, settings.USER_INFO_LAST_LOGINS_LIMIT)
	if err != nil {
		return nil, err
	}
	return &Response{
		Data: scal.M{
			"email":          email,
			"fullName":       userModel.FullName,
			"defaultGroupId": *userModel.DefaultGroupId,
			"emailConfirmed": userModel.EmailConfirmed,
			//"locked": userModel.Locked,

			"lastLogoutTime": userModel.LastLogoutTime,

			"lastLogins": lastLogins,
		},
	}, nil
}

func GetUserLoginHistory(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	// -----------------------------------------------
	const defaultLimit = settings.USER_LOGIN_HISTORY_DEFAULT_LIMIT
	limit, err := req.GetIntDefault("limit", defaultLimit)
	if err != nil {
		return nil, err
	}
	if limit <= 0 {
		limit = defaultLimit
		// otherwise db will return everything
	}
	lastLogins, err := loadLastLogins(req, userModel, limit)
	if err != nil {
		return nil, err
	}
	return &Response{
		Data: scal.M{
			"lastLogins": lastLogins,
		},
	}, nil
}

func GetUserDataFull(req Request) (*Response, error) {
	userModel, err := CheckAuth(req)
	if err != nil {
		return nil, err
	}
	email := userModel.Email
	// -----------------------------------------------
	db, err := storage.GetDB()
	if err != nil {
		return nil, NewError(Unavailable, "", err)
	}

	lastLogins, err := loadLastLogins(req, userModel, settings.USER_INFO_LAST_LOGINS_LIMIT)
	if err != nil {
		return nil, err
	}

	userData := scal.M{
		"email":          email,
		"fullName":       userModel.FullName,
		"defaultGroupId": userModel.DefaultGroupId,
		"emailConfirmed": userModel.EmailConfirmed,
		//"locked": userModel.Locked,
		"lastLogoutTime": userModel.LastLogoutTime,
		"lastLogins":     lastLogins,
	}

	groupCond := db.NewCondition(storage.OR).
		Equals("ownerEmail", email).
		Includes("readAccessEmails", email)

	var groups []event_lib.ListGroupsRow
	err = db.FindAll(&groups, &storage.FindInput{
		Collection: storage.C_group,
		Condition:  groupCond,
		SortBy:     "_id",
	})
	if err != nil {
		return nil, NewError(Internal, "", err)
	}
	if groups == nil {
		groups = make([]event_lib.ListGroupsRow, 0)
	}

	events, err := getMyEventsFullData(db, email, nil)
	if err != nil {
		return nil, err
	}

	return &Response{
		Data: scal.M{
			"user":   userData,
			"groups": groups,
			"events": events,
		},
	}, nil
}
<|endoftext|>


<commit_before>package itchio

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"reflect"
	"strings"
	"time"
)

type Client struct {
	Key           string
	HTTPClient    *http.Client
	BaseURL       string
	RetryPatterns []time.Duration
}

type Response struct {
	Errors []string
}

type User struct {
	ID       int64
	Username string
	CoverUrl string `json:"cover_url"`
}

type Game struct {
	ID  int64
	Url string
}

type Upload struct {
	ID       int64
	Filename string
	Size     int64

	OSX     bool `json:"p_osx"`
	Linux   bool `json:"p_linux"`
	Windows bool `json:"p_windows"`
	Android bool `json:"p_android"`
}

func defaultRetryPatterns() []time.Duration {
	return []time.Duration{
		1 * time.Second,
		2 * time.Second,
		4 * time.Second,
		8 * time.Second,
		16 * time.Second,
	}
}

func ClientWithKey(key string) *Client {
	return &Client{
		Key:           key,
		HTTPClient:    http.DefaultClient,
		BaseURL:       "https://itch.io/api/1",
		RetryPatterns: defaultRetryPatterns(),
	}
}

type StatusResponse struct {
	Response

	Success bool
}

func (c *Client) WharfStatus() (r StatusResponse, err error) {
	path := c.MakePath("wharf/status")

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type MyGamesResponse struct {
	Response

	Games []Game
}

func (c *Client) MyGames() (r MyGamesResponse, err error) {
	path := c.MakePath("my-games")
	log.Printf("Requesting %s\n", path)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type GameUploadsResponse struct {
	Response

	Uploads []Upload `json:"uploads"`
}

func (c *Client) GameUploads(gameID int64) (r GameUploadsResponse, err error) {
	path := c.MakePath("game/%d/uploads", gameID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type UploadDownloadResponse struct {
	Response

	Url string
}

func (c *Client) UploadDownload(uploadID int64) (r UploadDownloadResponse, err error) {
	path := c.MakePath("upload/%d/download", uploadID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return r, err
}

type NewBuildResponse struct {
	Response

	Build struct {
		ID          int64 `json:"id"`
		UploadID    int64 `json:"upload_id"`
		ParentBuild struct {
			ID int64 `json:"id"`
		} `json:"parent_build"`
	}
}

func (c *Client) CreateBuild(target string, channel string, userVersion string) (r NewBuildResponse, err error) {
	path := c.MakePath("wharf/builds")

	form := url.Values{}
	form.Add("target", target)
	form.Add("channel", channel)
	if userVersion != "" {
		form.Add("user_version", userVersion)
	}

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type BuildFileInfo struct {
	ID      int64
	Size    int64
	State   BuildFileState
	Type    BuildFileType
	SubType BuildFileSubType

	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

type BuildInfo struct {
	ID            int64
	ParentBuildID int64 `json:"parent_build_id"`
	State         BuildState

	Files []*BuildFileInfo

	User      User
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

type ChannelInfo struct {
	Name string
	Tags string

	Upload  Upload
	Head    *BuildInfo `json:"head"`
	Pending *BuildInfo `json:"pending"`
}

type ListChannelsResponse struct {
	Response

	Channels map[string]ChannelInfo
}

type GetChannelResponse struct {
	Response

	Channel ChannelInfo
}

func (c *Client) ListChannels(target string) (r ListChannelsResponse, err error) {
	form := url.Values{}
	form.Add("target", target)
	path := c.MakePath("wharf/channels?%s", form.Encode())

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

func (c *Client) GetChannel(target string, channel string) (r GetChannelResponse, err error) {
	form := url.Values{}
	form.Add("target", target)
	path := c.MakePath("wharf/channels/%s?%s", channel, form.Encode())

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type BuildFileType string

const (
	BuildFileType_PATCH     BuildFileType = "patch"
	BuildFileType_ARCHIVE                 = "archive"
	BuildFileType_SIGNATURE               = "signature"
)

type BuildFileSubType string

const (
	BuildFileSubType_DEFAULT   BuildFileSubType = "default"
	BuildFileSubType_GZIP                       = "gzip"
	BuildFileSubType_OPTIMIZED                  = "optimized"
)

type BuildState string

const (
	BuildState_STARTED    BuildState = "started"
	BuildState_PROCESSING            = "processing"
	BuildState_COMPLETED             = "completed"
	BuildState_FAILED                = "failed"
)

type BuildFileState string

const (
	BuildFileState_CREATED   BuildFileState = "created"
	BuildFileState_UPLOADING                = "uploading"
	BuildFileState_UPLOADED                 = "uploaded"
	BuildFileState_FAILED                   = "failed"
)

type ListBuildFilesResponse struct {
	Response

	Files []BuildFileInfo
}

func (c *Client) ListBuildFiles(buildID int64) (r ListBuildFilesResponse, err error) {
	path := c.MakePath("wharf/builds/%d/files", buildID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type NewBuildFileResponse struct {
	Response

	File struct {
		ID           int64
		UploadURL    string            `json:"upload_url"`
		UploadParams map[string]string `json:"upload_params"`
	}
}

func (c *Client) CreateBuildFile(buildID int64, fileType BuildFileType) (r NewBuildFileResponse, err error) {
	path := c.MakePath("wharf/builds/%d/files", buildID)

	form := url.Values{}
	form.Add("type", string(fileType))

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type FinalizeBuildFileResponse struct {
	Response
}

func (c *Client) FinalizeBuildFile(buildID int64, fileID int64, size int64) (r FinalizeBuildFileResponse, err error) {
	path := c.MakePath("wharf/builds/%d/files/%d", buildID, fileID)

	form := url.Values{}
	form.Add("size", fmt.Sprintf("%d", size))

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type DownloadBuildFileResponse struct {
	Response

	URL string
}

func (c *Client) DownloadBuildFile(buildID int64, fileID int64) (reader io.ReadCloser, err error) {
	path := c.MakePath("wharf/builds/%d/files/%d/download", buildID, fileID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	var r DownloadBuildFileResponse
	err = ParseAPIResponse(&r, resp)
	if err != nil {
		return
	}

	req, err := http.NewRequest("GET", r.URL, nil)
	if err != nil {
		return
	}

	// not an API request, going directly with http's DefaultClient
	dlResp, err := http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	if dlResp.StatusCode != 200 {
		err = fmt.Errorf("Can't download: %s", dlResp.Status)
		return
	}

	reader = dlResp.Body
	return
}

type BuildEventType string

const (
	BuildEvent_JOB_STARTED   BuildEventType = "job_started"
	BuildEvent_JOB_FAILED                   = "job_failed"
	BuildEvent_JOB_COMPLETED                = "job_completed"
)

type BuildEventData map[string]interface{}

type NewBuildEventResponse struct {
	Response
}

func (c *Client) CreateBuildEvent(buildID int64, eventType BuildEventType, message string, data BuildEventData) (r NewBuildEventResponse, err error) {
	path := c.MakePath("wharf/builds/%d/events", buildID)

	form := url.Values{}
	form.Add("type", string(eventType))
	form.Add("message", message)

	jsonData, err := json.Marshal(data)
	if err != nil {
		return
	}
	form.Add("data", string(jsonData))

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type BuildEvent struct {
	Type    BuildEventType
	Message string
	Data    BuildEventData
}

type ListBuildEventsResponse struct {
	Response

	Events []BuildEvent
}

func (c *Client) ListBuildEvents(buildID int64) (r ListBuildEventsResponse, err error) {
	path := c.MakePath("wharf/builds/%d/events", buildID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

// Helpers

func (c *Client) Get(url string) (*http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

func (c *Client) PostForm(url string, data url.Values) (*http.Response, error) {
	req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	return c.Do(req)
}

func (c *Client) Do(req *http.Request) (*http.Response, error) {
	if strings.HasPrefix(c.Key, "jwt:") {
		req.Header.Add("Authorization", strings.Split(c.Key, ":")[1])
	}

	var res *http.Response
	var err error

	retryPatterns := append(c.RetryPatterns, time.Millisecond)

	for _, sleepTime := range retryPatterns {
		res, err = c.HTTPClient.Do(req)
		if res.StatusCode == 503 {
			// Rate limited, try again according to patterns.
			// following https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload#exp-backoff to the letter
			res.Body.Close()
			time.Sleep(sleepTime + time.Duration(rand.Int()%1000)*time.Millisecond)
		} else {
			break
		}
	}

	return res, err
}

func (c *Client) MakePath(format string, a ...interface{}) string {
	base := strings.Trim(c.BaseURL, "/")
	subPath := strings.Trim(fmt.Sprintf(format, a...), "/")

	var key string
	if strings.HasPrefix(c.Key, "jwt:") {
		key = "jwt"
	} else {
		key = c.Key
	}
	return fmt.Sprintf("%s/%s/%s", base, key, subPath)
}

func ParseAPIResponse(dst interface{}, res *http.Response) error {
	if res == nil || res.Body == nil {
		return fmt.Errorf("No response from server")
	}

	bodyReader := res.Body
	defer bodyReader.Close()

	if res.StatusCode/100 != 2 {
		return fmt.Errorf("Server returned %s for %s", res.Status, res.Request.URL.String())
	}

	err := json.NewDecoder(bodyReader).Decode(dst)
	if err != nil {
		return err
	}

	errs := reflect.Indirect(reflect.ValueOf(dst)).FieldByName("Errors")
	if errs.Len() > 0 {
		// TODO: handle other errors too
		return errors.New(errs.Index(0).String())
	}

	return nil
}
<commit_msg>Let allow specifying subtype + uploadtype when creating build file<commit_after>package itchio

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"reflect"
	"strings"
	"time"
)

type Client struct {
	Key           string
	HTTPClient    *http.Client
	BaseURL       string
	RetryPatterns []time.Duration
}

type Response struct {
	Errors []string
}

type User struct {
	ID       int64
	Username string
	CoverUrl string `json:"cover_url"`
}

type Game struct {
	ID  int64
	Url string
}

type Upload struct {
	ID       int64
	Filename string
	Size     int64

	OSX     bool `json:"p_osx"`
	Linux   bool `json:"p_linux"`
	Windows bool `json:"p_windows"`
	Android bool `json:"p_android"`
}

func defaultRetryPatterns() []time.Duration {
	return []time.Duration{
		1 * time.Second,
		2 * time.Second,
		4 * time.Second,
		8 * time.Second,
		16 * time.Second,
	}
}

func ClientWithKey(key string) *Client {
	return &Client{
		Key:           key,
		HTTPClient:    http.DefaultClient,
		BaseURL:       "https://itch.io/api/1",
		RetryPatterns: defaultRetryPatterns(),
	}
}

type StatusResponse struct {
	Response

	Success bool
}

func (c *Client) WharfStatus() (r StatusResponse, err error) {
	path := c.MakePath("wharf/status")

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type MyGamesResponse struct {
	Response

	Games []Game
}

func (c *Client) MyGames() (r MyGamesResponse, err error) {
	path := c.MakePath("my-games")
	log.Printf("Requesting %s\n", path)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type GameUploadsResponse struct {
	Response

	Uploads []Upload `json:"uploads"`
}

func (c *Client) GameUploads(gameID int64) (r GameUploadsResponse, err error) {
	path := c.MakePath("game/%d/uploads", gameID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type UploadDownloadResponse struct {
	Response

	Url string
}

func (c *Client) UploadDownload(uploadID int64) (r UploadDownloadResponse, err error) {
	path := c.MakePath("upload/%d/download", uploadID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return r, err
}

type NewBuildResponse struct {
	Response

	Build struct {
		ID          int64 `json:"id"`
		UploadID    int64 `json:"upload_id"`
		ParentBuild struct {
			ID int64 `json:"id"`
		} `json:"parent_build"`
	}
}

func (c *Client) CreateBuild(target string, channel string, userVersion string) (r NewBuildResponse, err error) {
	path := c.MakePath("wharf/builds")

	form := url.Values{}
	form.Add("target", target)
	form.Add("channel", channel)
	if userVersion != "" {
		form.Add("user_version", userVersion)
	}

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type BuildFileInfo struct {
	ID      int64
	Size    int64
	State   BuildFileState
	Type    BuildFileType
	SubType BuildFileSubType

	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

type BuildInfo struct {
	ID            int64
	ParentBuildID int64 `json:"parent_build_id"`
	State         BuildState

	Files []*BuildFileInfo

	User      User
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

type ChannelInfo struct {
	Name string
	Tags string

	Upload  Upload
	Head    *BuildInfo `json:"head"`
	Pending *BuildInfo `json:"pending"`
}

type ListChannelsResponse struct {
	Response

	Channels map[string]ChannelInfo
}

type GetChannelResponse struct {
	Response

	Channel ChannelInfo
}

func (c *Client) ListChannels(target string) (r ListChannelsResponse, err error) {
	form := url.Values{}
	form.Add("target", target)
	path := c.MakePath("wharf/channels?%s", form.Encode())

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

func (c *Client) GetChannel(target string, channel string) (r GetChannelResponse, err error) {
	form := url.Values{}
	form.Add("target", target)
	path := c.MakePath("wharf/channels/%s?%s", channel, form.Encode())

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type BuildFileType string

const (
	BuildFileType_PATCH     BuildFileType = "patch"
	BuildFileType_ARCHIVE                 = "archive"
	BuildFileType_SIGNATURE               = "signature"
)

type BuildFileSubType string

const (
	BuildFileSubType_DEFAULT   BuildFileSubType = "default"
	BuildFileSubType_GZIP                       = "gzip"
	BuildFileSubType_OPTIMIZED                  = "optimized"
)

type UploadType string

const (
	UploadType_MULTIPART UploadType = "multipart"
	UploadType_RESUMABLE            = "resumable"
)

type BuildState string

const (
	BuildState_STARTED    BuildState = "started"
	BuildState_PROCESSING            = "processing"
	BuildState_COMPLETED             = "completed"
	BuildState_FAILED                = "failed"
)

type BuildFileState string

const (
	BuildFileState_CREATED   BuildFileState = "created"
	BuildFileState_UPLOADING                = "uploading"
	BuildFileState_UPLOADED                 = "uploaded"
	BuildFileState_FAILED                   = "failed"
)

type ListBuildFilesResponse struct {
	Response

	Files []BuildFileInfo
}

func (c *Client) ListBuildFiles(buildID int64) (r ListBuildFilesResponse, err error) {
	path := c.MakePath("wharf/builds/%d/files", buildID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type NewBuildFileResponse struct {
	Response

	File struct {
		ID           int64
		UploadURL    string            `json:"upload_url"`
		UploadParams map[string]string `json:"upload_params"`
	}
}

func (c *Client) CreateBuildFile(buildID int64, fileType BuildFileType, fileSubType, subType BuildFileSubType, uploadType UploadType) (r NewBuildFileResponse, err error) {
	path := c.MakePath("wharf/builds/%d/files", buildID)

	form := url.Values{}
	form.Add("type", string(fileType))
	if subType != "" {
		form.Add("sub_type", string(subType))
	}
	if uploadType != "" {
		form.Add("upload_type", string(uploadType))
	}

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type FinalizeBuildFileResponse struct {
	Response
}

func (c *Client) FinalizeBuildFile(buildID int64, fileID int64, size int64) (r FinalizeBuildFileResponse, err error) {
	path := c.MakePath("wharf/builds/%d/files/%d", buildID, fileID)

	form := url.Values{}
	form.Add("size", fmt.Sprintf("%d", size))

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type DownloadBuildFileResponse struct {
	Response

	URL string
}

func (c *Client) DownloadBuildFile(buildID int64, fileID int64) (reader io.ReadCloser, err error) {
	path := c.MakePath("wharf/builds/%d/files/%d/download", buildID, fileID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	var r DownloadBuildFileResponse
	err = ParseAPIResponse(&r, resp)
	if err != nil {
		return
	}

	req, err := http.NewRequest("GET", r.URL, nil)
	if err != nil {
		return
	}

	// not an API request, going directly with http's DefaultClient
	dlResp, err := http.DefaultClient.Do(req)
	if err != nil {
		return
	}

	if dlResp.StatusCode != 200 {
		err = fmt.Errorf("Can't download: %s", dlResp.Status)
		return
	}

	reader = dlResp.Body
	return
}

type BuildEventType string

const (
	BuildEvent_JOB_STARTED   BuildEventType = "job_started"
	BuildEvent_JOB_FAILED                   = "job_failed"
	BuildEvent_JOB_COMPLETED                = "job_completed"
)

type BuildEventData map[string]interface{}

type NewBuildEventResponse struct {
	Response
}

func (c *Client) CreateBuildEvent(buildID int64, eventType BuildEventType, message string, data BuildEventData) (r NewBuildEventResponse, err error) {
	path := c.MakePath("wharf/builds/%d/events", buildID)

	form := url.Values{}
	form.Add("type", string(eventType))
	form.Add("message", message)

	jsonData, err := json.Marshal(data)
	if err != nil {
		return
	}
	form.Add("data", string(jsonData))

	resp, err := c.PostForm(path, form)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

type BuildEvent struct {
	Type    BuildEventType
	Message string
	Data    BuildEventData
}

type ListBuildEventsResponse struct {
	Response

	Events []BuildEvent
}

func (c *Client) ListBuildEvents(buildID int64) (r ListBuildEventsResponse, err error) {
	path := c.MakePath("wharf/builds/%d/events", buildID)

	resp, err := c.Get(path)
	if err != nil {
		return
	}

	err = ParseAPIResponse(&r, resp)
	return
}

// Helpers

func (c *Client) Get(url string) (*http.Response, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	return c.Do(req)
}

func (c *Client) PostForm(url string, data url.Values) (*http.Response, error) {
	req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	return c.Do(req)
}

func (c *Client) Do(req *http.Request) (*http.Response, error) {
	if strings.HasPrefix(c.Key, "jwt:") {
		req.Header.Add("Authorization", strings.Split(c.Key, ":")[1])
	}

	var res *http.Response
	var err error

	retryPatterns := append(c.RetryPatterns, time.Millisecond)

	for _, sleepTime := range retryPatterns {
		res, err = c.HTTPClient.Do(req)
		if res.StatusCode == 503 {
			// Rate limited, try again according to patterns.
			// following https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload#exp-backoff to the letter
			res.Body.Close()
			time.Sleep(sleepTime + time.Duration(rand.Int()%1000)*time.Millisecond)
		} else {
			break
		}
	}

	return res, err
}

func (c *Client) MakePath(format string, a ...interface{}) string {
	base := strings.Trim(c.BaseURL, "/")
	subPath := strings.Trim(fmt.Sprintf(format, a...), "/")

	var key string
	if strings.HasPrefix(c.Key, "jwt:") {
		key = "jwt"
	} else {
		key = c.Key
	}
	return fmt.Sprintf("%s/%s/%s", base, key, subPath)
}

func ParseAPIResponse(dst interface{}, res *http.Response) error {
	if res == nil || res.Body == nil {
		return fmt.Errorf("No response from server")
	}

	bodyReader := res.Body
	defer bodyReader.Close()

	if res.StatusCode/100 != 2 {
		return fmt.Errorf("Server returned %s for %s", res.Status, res.Request.URL.String())
	}

	err := json.NewDecoder(bodyReader).Decode(dst)
	if err != nil {
		return err
	}

	errs := reflect.Indirect(reflect.ValueOf(dst)).FieldByName("Errors")
	if errs.Len() > 0 {
		// TODO: handle other errors too
		return errors.New(errs.Index(0).String())
	}

	return nil
}
<|endoftext|>


<commit_before>package wrapper

import (
	"net/http"

	"code.cloudfoundry.org/cli/api/cfnetworking"
)

// RetryRequest is a wrapper that retries failed requests if they contain a 5XX
// status code.
type RetryRequest struct {
	maxRetries int
	connection cfnetworking.Connection
}

// NewRetryRequest returns a pointer to a RetryRequest wrapper.
func NewRetryRequest(maxRetries int) *RetryRequest {
	return &RetryRequest{
		maxRetries: maxRetries,
	}
}

// Wrap sets the connection in the RetryRequest and returns itself.
func (retry *RetryRequest) Wrap(innerconnection cfnetworking.Connection) cfnetworking.Connection {
	retry.connection = innerconnection
	return retry
}

// Make retries the request if it comes back with certain status codes.
func (retry *RetryRequest) Make(request *cfnetworking.Request, passedResponse *cfnetworking.Response) error {
	var err error

	for i := 0; i < retry.maxRetries+1; i += 1 {
		err = retry.connection.Make(request, passedResponse)
		if err == nil {
			return nil
		}

		if passedResponse.HTTPResponse.StatusCode == http.StatusBadGateway ||
			passedResponse.HTTPResponse.StatusCode == http.StatusServiceUnavailable ||
			passedResponse.HTTPResponse.StatusCode == http.StatusGatewayTimeout ||
			(passedResponse.HTTPResponse.StatusCode >= 400 && passedResponse.HTTPResponse.StatusCode < 500) {
			break
		}

		// Reset the request body prior to the next retry
		resetErr := request.ResetBody()
		if resetErr != nil {
			return resetErr
		}
	}
	return err
}
<commit_msg>don't retry on http error responses<commit_after>package wrapper

import (
	"net/http"

	"code.cloudfoundry.org/cli/api/cfnetworking"
)

// RetryRequest is a wrapper that retries failed requests if they contain a 5XX
// status code.
type RetryRequest struct {
	maxRetries int
	connection cfnetworking.Connection
}

// NewRetryRequest returns a pointer to a RetryRequest wrapper.
func NewRetryRequest(maxRetries int) *RetryRequest {
	return &RetryRequest{
		maxRetries: maxRetries,
	}
}

// Wrap sets the connection in the RetryRequest and returns itself.
func (retry *RetryRequest) Wrap(innerconnection cfnetworking.Connection) cfnetworking.Connection {
	retry.connection = innerconnection
	return retry
}

// Make retries the request if it comes back with certain status codes.
func (retry *RetryRequest) Make(request *cfnetworking.Request, passedResponse *cfnetworking.Response) error {
	var err error

	for i := 0; i < retry.maxRetries+1; i += 1 {
		err = retry.connection.Make(request, passedResponse)
		if err == nil {
			return nil
		}

		if passedResponse.HTTPResponse != nil &&
			(passedResponse.HTTPResponse.StatusCode == http.StatusBadGateway ||
				passedResponse.HTTPResponse.StatusCode == http.StatusServiceUnavailable ||
				passedResponse.HTTPResponse.StatusCode == http.StatusGatewayTimeout ||
				(passedResponse.HTTPResponse.StatusCode >= 400 && passedResponse.HTTPResponse.StatusCode < 500)) {
			break
		}

		// Reset the request body prior to the next retry
		resetErr := request.ResetBody()
		if resetErr != nil {
			return resetErr
		}
	}
	return err
}
<|endoftext|>