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

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/go-lsst/fcs-lpc-motor-ctl/bench"
	"github.com/go-lsst/ncs/drivers/m702"
)

var (
	errInvalidHTTPMethod = errors.New("invalid HTTP method")
)

func (srv *server) apiMonHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	var infos [2]motorInfos
	for i, m := range srv.motors() {
		info, err := m.infos(1 * time.Second)
		if err != nil {
			srv.apiError(w, err, http.StatusServiceUnavailable)
			return
		}
		infos[i] = info
	}
	var resp = struct {
		Err   string        `json:"error,omitempty"`
		Code  int           `json:"code"`
		Infos [2]motorInfos `json:"infos"`
	}{
		Err:   "",
		Code:  http.StatusOK,
		Infos: infos,
	}

	var buf bytes.Buffer
	err := json.NewEncoder(&buf).Encode(resp)
	if err != nil {
		srv.apiError(w, fmt.Errorf("could not encode monitoring infos to JSON: %v", err), http.StatusInternalServerError)
		return
	}
	_, err = io.Copy(w, &buf)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing JSON response: %v", err), http.StatusInternalServerError)
		return
	}
}

func (srv *server) apiCmdReqReadyHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	p := newParameter(bench.ParamCmdReady)
	codec.PutUint32(p.Data[:], uint32(req.Value))
	err = m.Motor().WriteParam(p)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
		return
	}
	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqFindHomeHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	params := append([]m702.Parameter{},
		newParameter(bench.ParamCmdReady),
		newParameter(bench.ParamModePos),
		newParameter(bench.ParamHome),
		newParameter(bench.ParamCmdReady),
	)

	codec.PutUint32(params[0].Data[:], 0)
	codec.PutUint32(params[1].Data[:], 0)
	codec.PutUint32(params[2].Data[:], 1)
	codec.PutUint32(params[3].Data[:], 1)

	for _, p := range params {
		err = m.Motor().WriteParam(p)
		if err != nil {
			srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
			return
		}
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqPosHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	params := append([]m702.Parameter{},
		newParameter(bench.ParamCmdReady),
		newParameter(bench.ParamModePos),
		newParameter(bench.ParamHome),
		newParameter(bench.ParamCmdReady),
	)

	codec.PutUint32(params[0].Data[:], 0)
	codec.PutUint32(params[1].Data[:], 1)
	codec.PutUint32(params[2].Data[:], 0)
	codec.PutUint32(params[3].Data[:], 1)

	for _, p := range params {
		err = m.Motor().WriteParam(p)
		if err != nil {
			srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
			return
		}
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqRPMHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	if req.Value > 3000 {
		srv.apiError(w, fmt.Errorf("invalid RPM value (%v > 3000)", req.Value), http.StatusBadRequest)
		return
	}
	if req.Value < 0 {
		srv.apiError(w, fmt.Errorf("invalid RPM value (%v < 0)", req.Value), http.StatusBadRequest)
		return
	}

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	p := newParameter(bench.ParamRPMs)
	codec.PutUint32(p.Data[:], uint32(req.Value))
	err = m.Motor().WriteParam(p)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
		return
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqAnglePosHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"
	if req.Value > +90 {
		srv.apiError(w, fmt.Errorf("invalid angle position (%v > +90.0)", req.Value), http.StatusBadRequest)
		return
	}
	if req.Value < -90 {
		srv.apiError(w, fmt.Errorf("invalid angle position (%v < -90.0)", req.Value), http.StatusBadRequest)
		return
	}
	req.Value *= 10

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	p := newParameter(bench.ParamWritePos)
	codec.PutUint32(p.Data[:], uint32(req.Value))
	err = m.Motor().WriteParam(p)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
		return
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqUploadCmdsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	buf := new(bytes.Buffer)
	script := newScripter(srv, m.Motor())
	cmds := bytes.NewReader([]byte(req.Cmds))
	err = script.run(m.Motor(), cmds, buf)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error running script: %v", err), http.StatusInternalServerError)
		return
	}

	var reply = struct {
		Err    string `json:"error"`
		Code   int    `json:"code"`
		Script string `json:"script"`
	}{
		Err:    "",
		Code:   http.StatusOK,
		Script: string(buf.Bytes()),
	}

	var o = new(bytes.Buffer)
	err = json.NewEncoder(o).Encode(reply)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error encoding JSON reply: %v", err), http.StatusInternalServerError)
		return
	}

	_, err = io.Copy(w, o)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error sending JSON reply: %v", err), http.StatusInternalServerError)
		return
	}
}

func (srv *server) apiOK(w http.ResponseWriter, code int) {
	http.Error(w, fmt.Sprintf("{error:%q, code:%v}", "", code), code)
}

func (srv *server) apiError(w http.ResponseWriter, err error, code int) {
	http.Error(w, fmt.Sprintf("{error:%q, code:%v}", err.Error(), code), code)
}

func (srv *server) apiCheck(req cmdRequest, w http.ResponseWriter, r *http.Request) (*motor, bool) {
	m, err := srv.getMotor(req.Motor)
	if err != nil {
		srv.apiError(w, err, http.StatusBadRequest)
		return nil, false
	}

	if online, err := m.isOnline(1 * time.Second); err != nil || !online {
		if err != nil {
			srv.apiError(w, err, http.StatusServiceUnavailable)
			return nil, false
		}
		srv.apiError(w, bench.ErrMotorOffline, http.StatusServiceUnavailable)
		return nil, false
	}

	if m.isHWLocked() {
		srv.apiError(w, bench.ErrMotorHWLock, http.StatusServiceUnavailable)
		return nil, false
	}

	if m.isManual() {
		srv.apiError(w, bench.ErrMotorManual, http.StatusServiceUnavailable)
		return nil, false
	}

	return m, true
}

func (srv *server) apiAuthenticated(h func(w http.ResponseWriter, r *http.Request), needACL bool) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cli, cookie, err := srv.checkCredentials(w, r)
		if err != nil {
			srv.apiError(w, err, http.StatusInternalServerError)
			return
		}

		user, pass, ok := r.BasicAuth()
		if !ok || !srv.authenticate(user, pass) {
			srv.apiError(w, errUserAuth, http.StatusForbidden)
			return
		}

		cli.auth = true
		cli.name = user
		srv.session.set(cookie, cli)

		c := client{}
		c.setACL(cli.name)

		if needACL && c.acl != 1 {
			srv.apiError(w, errUserPerm, http.StatusForbidden)
			return
		}

		http.SetCookie(w, &http.Cookie{
			Name:  "FCS_USER",
			Value: cli.name,
		})

		w.Header().Set("Content-Type", "application/json")
		h(w, r)
		return
	}
}
<commit_msg>api: dont display error if there is none<commit_after>// Copyright ©2016 The go-lsst Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/go-lsst/fcs-lpc-motor-ctl/bench"
	"github.com/go-lsst/ncs/drivers/m702"
)

var (
	errInvalidHTTPMethod = errors.New("invalid HTTP method")
)

func (srv *server) apiMonHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	var infos [2]motorInfos
	for i, m := range srv.motors() {
		info, err := m.infos(1 * time.Second)
		if err != nil {
			srv.apiError(w, err, http.StatusServiceUnavailable)
			return
		}
		infos[i] = info
	}
	var resp = struct {
		Err   string        `json:"error,omitempty"`
		Code  int           `json:"code"`
		Infos [2]motorInfos `json:"infos"`
	}{
		Err:   "",
		Code:  http.StatusOK,
		Infos: infos,
	}

	var buf bytes.Buffer
	err := json.NewEncoder(&buf).Encode(resp)
	if err != nil {
		srv.apiError(w, fmt.Errorf("could not encode monitoring infos to JSON: %v", err), http.StatusInternalServerError)
		return
	}
	_, err = io.Copy(w, &buf)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing JSON response: %v", err), http.StatusInternalServerError)
		return
	}
}

func (srv *server) apiCmdReqReadyHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	p := newParameter(bench.ParamCmdReady)
	codec.PutUint32(p.Data[:], uint32(req.Value))
	err = m.Motor().WriteParam(p)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
		return
	}
	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqFindHomeHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	params := append([]m702.Parameter{},
		newParameter(bench.ParamCmdReady),
		newParameter(bench.ParamModePos),
		newParameter(bench.ParamHome),
		newParameter(bench.ParamCmdReady),
	)

	codec.PutUint32(params[0].Data[:], 0)
	codec.PutUint32(params[1].Data[:], 0)
	codec.PutUint32(params[2].Data[:], 1)
	codec.PutUint32(params[3].Data[:], 1)

	for _, p := range params {
		err = m.Motor().WriteParam(p)
		if err != nil {
			srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
			return
		}
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqPosHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	params := append([]m702.Parameter{},
		newParameter(bench.ParamCmdReady),
		newParameter(bench.ParamModePos),
		newParameter(bench.ParamHome),
		newParameter(bench.ParamCmdReady),
	)

	codec.PutUint32(params[0].Data[:], 0)
	codec.PutUint32(params[1].Data[:], 1)
	codec.PutUint32(params[2].Data[:], 0)
	codec.PutUint32(params[3].Data[:], 1)

	for _, p := range params {
		err = m.Motor().WriteParam(p)
		if err != nil {
			srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
			return
		}
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqRPMHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	if req.Value > 3000 {
		srv.apiError(w, fmt.Errorf("invalid RPM value (%v > 3000)", req.Value), http.StatusBadRequest)
		return
	}
	if req.Value < 0 {
		srv.apiError(w, fmt.Errorf("invalid RPM value (%v < 0)", req.Value), http.StatusBadRequest)
		return
	}

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	p := newParameter(bench.ParamRPMs)
	codec.PutUint32(p.Data[:], uint32(req.Value))
	err = m.Motor().WriteParam(p)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
		return
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqAnglePosHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"
	if req.Value > +90 {
		srv.apiError(w, fmt.Errorf("invalid angle position (%v > +90.0)", req.Value), http.StatusBadRequest)
		return
	}
	if req.Value < -90 {
		srv.apiError(w, fmt.Errorf("invalid angle position (%v < -90.0)", req.Value), http.StatusBadRequest)
		return
	}
	req.Value *= 10

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	p := newParameter(bench.ParamWritePos)
	codec.PutUint32(p.Data[:], uint32(req.Value))
	err = m.Motor().WriteParam(p)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error writing parameter %v to motor-%v: %v", p, m.name, err), http.StatusInternalServerError)
		return
	}

	srv.apiOK(w, http.StatusOK)
}

func (srv *server) apiCmdReqUploadCmdsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		srv.apiError(w, errInvalidHTTPMethod, http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	var req cmdRequest
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error decoding JSON request: %v", err), http.StatusBadRequest)
		return
	}
	req.tstamp = time.Now().UTC()
	req.Type = "ctl"

	m, ok := srv.apiCheck(req, w, r)
	if !ok {
		return
	}

	buf := new(bytes.Buffer)
	script := newScripter(srv, m.Motor())
	cmds := bytes.NewReader([]byte(req.Cmds))
	err = script.run(m.Motor(), cmds, buf)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error running script: %v", err), http.StatusInternalServerError)
		return
	}

	var reply = struct {
		Err    string `json:"error,omitempty"`
		Code   int    `json:"code"`
		Script string `json:"script"`
	}{
		Err:    "",
		Code:   http.StatusOK,
		Script: string(buf.Bytes()),
	}

	var o = new(bytes.Buffer)
	err = json.NewEncoder(o).Encode(reply)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error encoding JSON reply: %v", err), http.StatusInternalServerError)
		return
	}

	_, err = io.Copy(w, o)
	if err != nil {
		srv.apiError(w, fmt.Errorf("error sending JSON reply: %v", err), http.StatusInternalServerError)
		return
	}
}

func (srv *server) apiOK(w http.ResponseWriter, code int) {
	http.Error(w, fmt.Sprintf("{error:%q, code:%v}", "", code), code)
}

func (srv *server) apiError(w http.ResponseWriter, err error, code int) {
	http.Error(w, fmt.Sprintf("{error:%q, code:%v}", err.Error(), code), code)
}

func (srv *server) apiCheck(req cmdRequest, w http.ResponseWriter, r *http.Request) (*motor, bool) {
	m, err := srv.getMotor(req.Motor)
	if err != nil {
		srv.apiError(w, err, http.StatusBadRequest)
		return nil, false
	}

	if online, err := m.isOnline(1 * time.Second); err != nil || !online {
		if err != nil {
			srv.apiError(w, err, http.StatusServiceUnavailable)
			return nil, false
		}
		srv.apiError(w, bench.ErrMotorOffline, http.StatusServiceUnavailable)
		return nil, false
	}

	if m.isHWLocked() {
		srv.apiError(w, bench.ErrMotorHWLock, http.StatusServiceUnavailable)
		return nil, false
	}

	if m.isManual() {
		srv.apiError(w, bench.ErrMotorManual, http.StatusServiceUnavailable)
		return nil, false
	}

	return m, true
}

func (srv *server) apiAuthenticated(h func(w http.ResponseWriter, r *http.Request), needACL bool) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cli, cookie, err := srv.checkCredentials(w, r)
		if err != nil {
			srv.apiError(w, err, http.StatusInternalServerError)
			return
		}

		user, pass, ok := r.BasicAuth()
		if !ok || !srv.authenticate(user, pass) {
			srv.apiError(w, errUserAuth, http.StatusForbidden)
			return
		}

		cli.auth = true
		cli.name = user
		srv.session.set(cookie, cli)

		c := client{}
		c.setACL(cli.name)

		if needACL && c.acl != 1 {
			srv.apiError(w, errUserPerm, http.StatusForbidden)
			return
		}

		http.SetCookie(w, &http.Cookie{
			Name:  "FCS_USER",
			Value: cli.name,
		})

		w.Header().Set("Content-Type", "application/json")
		h(w, r)
		return
	}
}
<|endoftext|>


<commit_before>package routeros

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/jcelliott/lumber"
	"io"
	"net"
	"strings"
)

// A reply can contain multiple pairs. A pair is a string key->value.
// A reply can also contain subpairs, that is, a array of pair arrays.
type Reply struct {
	Pairs    []Pair
	SubPairs []map[string]string
}

func (r *Reply) GetPairVal(key string) (string, error) {
	for _, p := range r.Pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

func (r *Reply) GetSubPairByName(key string) (map[string]string, error) {
	for _, p := range r.SubPairs {
		if _, ok := p["name"]; ok {
			return p, nil
		}
	}
	return nil, errors.New("key not found")
}

func GetPairVal(pairs []Pair, key string) (string, error) {
	for _, p := range pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

// Client is a RouterOS API client.
type Client struct {
	// Network Address.
	// E.g. "10.0.0.1:8728" or "router.example.com:8728"
	address  string
	user     string
	password string
	logger   *lumber.Logger
	debug    bool     // debug logging enabled
	ready    bool     // Ready for work (login ok and connection not terminated)
	conn     net.Conn // Connection to pass around
}

// Pair is a Key-Value pair for RouterOS Attribute, Query, and Reply words
// use slices of pairs instead of map because we care about order
type Pair struct {
	Key   string
	Value string
	// Op is used for Query words to signify logical operations
	// valid operators are -, =, <, >
	// see http://wiki.mikrotik.com/wiki/Manual:API#Queries for details.
	Op string
}

type Query struct {
	Pairs    []Pair
	Op       string
	Proplist []string
}

func NewPair(key string, value string) *Pair {
	p := new(Pair)
	p.Key = key
	p.Value = value
	return p
}

// Create a new instance of the RouterOS API client
func New(address string) (*Client, error) {
	// basic validation of host address
	_, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}

	var c Client
	c.address = address

	return &c, nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) Connect(user string, password string) error {
	conn, err := net.Dial("tcp", c.address)
	if err != nil {
		return err
	}

	// stash conn in instance
	c.conn = conn

	// try to log in
	res, err := c.Call("/login", nil)
	if err != nil {
		return err
	}

	// handle challenge/response
	challengeEnc, err := res.GetPairVal("ret")
	if err != nil {
		return errors.New("Didn't get challenge from ROS")
	}
	challenge, err := hex.DecodeString(challengeEnc)
	if err != nil {
		return err
	}
	h := md5.New()
	io.WriteString(h, "\000")
	io.WriteString(h, password)
	h.Write(challenge)
	resp := fmt.Sprintf("00%x", h.Sum(nil))
	var loginParams []Pair
	loginParams = append(loginParams, *NewPair("name", password))
	loginParams = append(loginParams, *NewPair("response", resp))

	// try to log in again with challenge/response
	res, err = c.Call("/login", loginParams)
	if err != nil {
		return err
	}

	if len(res.Pairs) > 0 {
		return fmt.Errorf("Unexpected result on login: %+v", res)
	}

	return nil
}

func (c *Client) Query(command string, q Query) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// Set property list if present
	if len(q.Proplist) > 0 {
		proplist := fmt.Sprintf("=.proplist=%s", strings.Join(q.Proplist, ","))
		err = c.send(proplist)
		if err != nil {
			return Reply{}, err
		}
	}

	// send params if we got them
	if len(q.Pairs) > 0 {
		for _, v := range q.Pairs {
			word := fmt.Sprintf("?%s%s=%s", v.Op, v.Key, v.Value)
			c.send(word)
		}

		if q.Op != "" {
			word := fmt.Sprintf("?#%s", q.Op)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}

func (c *Client) Call(command string, params []Pair) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// send params if we got them
	if len(params) > 0 {
		for _, v := range params {
			word := fmt.Sprintf("=%s=%s", v.Key, v.Value)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}
<commit_msg>fixing pr with gspbn<commit_after>package routeros

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/jcelliott/lumber"
	"io"
	"net"
	"strings"
)

// A reply can contain multiple pairs. A pair is a string key->value.
// A reply can also contain subpairs, that is, a array of pair arrays.
type Reply struct {
	Pairs    []Pair
	SubPairs []map[string]string
}

func (r *Reply) GetPairVal(key string) (string, error) {
	for _, p := range r.Pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

func (r *Reply) GetSubPairByName(key string) (map[string]string, error) {
	for _, p := range r.SubPairs {
		if _, ok := p["name"]; ok {
			if p["name"] == key {
				return p, nil
			}
		}
	}
	return nil, errors.New("key not found")
}

func GetPairVal(pairs []Pair, key string) (string, error) {
	for _, p := range pairs {
		if p.Key == key {
			return p.Value, nil
		}
	}
	return "", errors.New("key not found")
}

// Client is a RouterOS API client.
type Client struct {
	// Network Address.
	// E.g. "10.0.0.1:8728" or "router.example.com:8728"
	address  string
	user     string
	password string
	logger   *lumber.Logger
	debug    bool     // debug logging enabled
	ready    bool     // Ready for work (login ok and connection not terminated)
	conn     net.Conn // Connection to pass around
}

// Pair is a Key-Value pair for RouterOS Attribute, Query, and Reply words
// use slices of pairs instead of map because we care about order
type Pair struct {
	Key   string
	Value string
	// Op is used for Query words to signify logical operations
	// valid operators are -, =, <, >
	// see http://wiki.mikrotik.com/wiki/Manual:API#Queries for details.
	Op string
}

type Query struct {
	Pairs    []Pair
	Op       string
	Proplist []string
}

func NewPair(key string, value string) *Pair {
	p := new(Pair)
	p.Key = key
	p.Value = value
	return p
}

// Create a new instance of the RouterOS API client
func New(address string) (*Client, error) {
	// basic validation of host address
	_, _, err := net.SplitHostPort(address)
	if err != nil {
		return nil, err
	}

	var c Client
	c.address = address

	return &c, nil
}

func (c *Client) Close() {
	c.conn.Close()
}

func (c *Client) Connect(user string, password string) error {
	conn, err := net.Dial("tcp", c.address)
	if err != nil {
		return err
	}

	// stash conn in instance
	c.conn = conn

	// try to log in
	res, err := c.Call("/login", nil)
	if err != nil {
		return err
	}

	// handle challenge/response
	challengeEnc, err := res.GetPairVal("ret")
	if err != nil {
		return errors.New("Didn't get challenge from ROS")
	}
	challenge, err := hex.DecodeString(challengeEnc)
	if err != nil {
		return err
	}
	h := md5.New()
	io.WriteString(h, "\000")
	io.WriteString(h, password)
	h.Write(challenge)
	resp := fmt.Sprintf("00%x", h.Sum(nil))
	var loginParams []Pair
	loginParams = append(loginParams, *NewPair("name", password))
	loginParams = append(loginParams, *NewPair("response", resp))

	// try to log in again with challenge/response
	res, err = c.Call("/login", loginParams)
	if err != nil {
		return err
	}

	if len(res.Pairs) > 0 {
		return fmt.Errorf("Unexpected result on login: %+v", res)
	}

	return nil
}

func (c *Client) Query(command string, q Query) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// Set property list if present
	if len(q.Proplist) > 0 {
		proplist := fmt.Sprintf("=.proplist=%s", strings.Join(q.Proplist, ","))
		err = c.send(proplist)
		if err != nil {
			return Reply{}, err
		}
	}

	// send params if we got them
	if len(q.Pairs) > 0 {
		for _, v := range q.Pairs {
			word := fmt.Sprintf("?%s%s=%s", v.Op, v.Key, v.Value)
			c.send(word)
		}

		if q.Op != "" {
			word := fmt.Sprintf("?#%s", q.Op)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}

func (c *Client) Call(command string, params []Pair) (Reply, error) {
	err := c.send(command)
	if err != nil {
		return Reply{}, err
	}

	// send params if we got them
	if len(params) > 0 {
		for _, v := range params {
			word := fmt.Sprintf("=%s=%s", v.Key, v.Value)
			c.send(word)
		}
	}

	// send terminator
	err = c.send("")
	if err != nil {
		return Reply{}, err
	}

	res, err := c.receive()
	if err != nil {
		return Reply{}, err
	}

	return res, nil
}
<|endoftext|>


<commit_before>package beegoAPI

import (
	"fmt"

	"github.com/astaxie/beego"
)

//API for Client
type API struct {
	beego.Controller
}

type ResponseObject struct {
	Code           int         `json:"code"`
	Message        string      `json:"message"`
	ResponseObject interface{} `json:"responseObject"`
}

func (api *API) BaseURL() string {
	var baseUrl string = api.Ctx.Input.Site() + fmt.Sprintf(":%d", api.Ctx.Input.Port())
	if api.Ctx.Input.Header("X-Forwarded-Host") != "" {
		baseUrl = api.Ctx.Input.Scheme() + "://" + api.Ctx.Input.Header("X-Forwarded-Host")
	}
	return baseUrl
}

func (api *API) ResponseJSON(results interface{}, code int, msg string) {
	response := &ResponseObject{
		Code:           code,
		Message:        msg,
		ResponseObject: results,
	}
	api.Data["json"] = response
	api.Ctx.Output.SetStatus(code)
	api.ServeJSON()
	return
}
<commit_msg>added: ResponseJSONWithCode<commit_after>package beegoAPI

import (
	"fmt"

	"github.com/astaxie/beego"
)

//API for Client
type API struct {
	beego.Controller
}

type ResponseObject struct {
	Code           int         `json:"code"`
	Message        string      `json:"message"`
	ResponseObject interface{} `json:"responseObject"`
}

type ResponseObjectWithCode struct {
	Code           int64       `json:"code"`
	Message        string      `json:"message"`
	ResponseObject interface{} `json:"responseObject"`
}

func (api *API) BaseURL() string {
	var baseUrl string = api.Ctx.Input.Site() + fmt.Sprintf(":%d", api.Ctx.Input.Port())
	if api.Ctx.Input.Header("X-Forwarded-Host") != "" {
		baseUrl = api.Ctx.Input.Scheme() + "://" + api.Ctx.Input.Header("X-Forwarded-Host")
	}
	return baseUrl
}

func (api *API) ResponseJSON(results interface{}, code int, msg string) {
	if results == nil {
		results = struct{}{}
	}
	response := &ResponseObject{
		Code:           code,
		Message:        msg,
		ResponseObject: results,
	}
	api.Data["json"] = response
	api.Ctx.Output.SetStatus(code)
	api.ServeJSON()
	return
}

func (api *API) ResponseJSONWithCode(results interface{}, statusCode int, code int64, msg string) {
	if results == nil {
		results = struct{}{}
	}
	response := &ResponseObjectWithCode{
		Code:           code,
		Message:        msg,
		ResponseObject: results,
	}
	api.Data["json"] = response
	api.Ctx.Output.SetStatus(statusCode)
	api.ServeJSON()
	return
}
<|endoftext|>


<commit_before>package facebook

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strings"

	"github.com/deiwin/facebook/model"
)

// API provides access to the Facebook API graph methods
type API interface {
	// GET /me
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/user#read
	Me() (*model.User, error)

	// GET /me/accounts
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/user/accounts#read
	Accounts() (*model.Accounts, error)

	// GET /{page-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/page/
	Page(pageID string) (*model.Page, error)

	// POST /{page-id}/feed
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/page/feed#publish
	PagePublish(pageAccessToken, pageID string, post *model.Post) (*model.PostResponse, error)

	// GET /{post-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/post#read
	Post(pageAccessToken, postID string) (*model.PostResponse, error)

	// POST /{post-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/post#updating
	PostUpdate(pageAccessToken, postID string, post *model.Post) error

	// DELETE /{post-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/post#deleting
	PostDelete(pageAccessToken, postID string) error
}

type api struct {
	*http.Client
	conf apiConf
}

func (a api) Me() (*model.User, error) {
	resp, err := a.get("/me", url.Values{})
	if err != nil {
		return nil, err
	}
	var user model.User
	err = json.Unmarshal(resp, &user)
	return &user, err
}

func (a api) Accounts() (*model.Accounts, error) {
	resp, err := a.get("/me/accounts", url.Values{})
	if err != nil {
		return nil, err
	}
	var accs model.Accounts
	err = json.Unmarshal(resp, &accs)
	return &accs, err
}

func (a api) Page(pageID string) (*model.Page, error) {
	resp, err := a.get("/"+pageID, url.Values{})
	if err != nil {
		return nil, err
	}
	var page model.Page
	err = json.Unmarshal(resp, &page)
	return &page, err
}

func (a api) PagePublish(pageAccessToken, pageID string, post *model.Post) (*model.PostResponse, error) {
	resp, err := a.postFormable(fmt.Sprintf("/%s/feed", pageID), url.Values{
		"access_token": {pageAccessToken},
	}, post)
	if err != nil {
		return nil, err
	}
	var respPost model.PostResponse
	err = json.Unmarshal(resp, &respPost)
	return &respPost, err
}

func (a api) Post(pageAccessToken, postID string) (*model.PostResponse, error) {
	fieldsToInclude := getJsonTagsForType(reflect.TypeOf(model.PostResponse{}))
	resp, err := a.getFields("/"+postID, url.Values{
		"access_token": {pageAccessToken},
	}, fieldsToInclude)
	if err != nil {
		return nil, err
	}
	var post model.PostResponse
	err = json.Unmarshal(resp, &post)
	return &post, err
}

func (a api) PostUpdate(pageAccessToken, postID string, post *model.Post) error {
	_, err := a.postFormable("/"+postID, url.Values{
		"access_token": {pageAccessToken},
	}, post)
	return err
}

func (a api) PostDelete(pageAccessToken, postID string) error {
	return a.delete(postID, url.Values{
		"access_token": {pageAccessToken},
	})
}

func getJsonTagsForType(t reflect.Type) []string {
	tags := make([]string, t.NumField())
	for i := 0; i < t.NumField(); i++ {
		tags[i] = t.Field(i).Tag.Get("json")
	}
	return tags
}

func (a api) delete(path string, data url.Values) error {
	url := fmt.Sprintf("%s/%s?%s", a.conf.graphURL, path, data.Encode())
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	if _, err = a.Do(req); err != nil {
		return err
	}
	return nil
}

func (a api) get(path string, data url.Values) ([]byte, error) {
	url := fmt.Sprintf("%s/%s?%s", a.conf.graphURL, path, data.Encode())
	resp, err := a.Get(url)
	return parseResponse(resp, err)
}

func (a api) getFields(path string, data url.Values, fields []string) ([]byte, error) {
	url := fmt.Sprintf("%s/%s?%s&fields=%s", a.conf.graphURL, path, data.Encode(), strings.Join(fields, ","))
	resp, err := a.Get(url)
	return parseResponse(resp, err)
}

func (a api) post(path string, data url.Values) ([]byte, error) {
	resp, err := a.PostForm(a.conf.graphURL+path, data)
	return parseResponse(resp, err)
}

func (a api) postFormable(path string, data url.Values, formable model.Formable) ([]byte, error) {
	form := formable.AsForm()
	for k, vs := range data {
		for _, v := range vs {
			form.Add(k, v)
		}
	}
	resp, err := a.PostForm(a.conf.graphURL+path, form)
	return parseResponse(resp, err)
}

func parseResponse(resp *http.Response, err error) ([]byte, error) {
	if err != nil {
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
		return nil, err
	}
	defer resp.Body.Close()
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return bytes, err
	}
	return bytes, parseError(bytes)
}

func parseError(bytes []byte) error {
	var resp struct {
		Error *Error `json:"error,omitempty"`
	}
	err := json.Unmarshal(bytes, &resp)
	if err != nil {
		return err
	}
	// If we were to just return resp.Error, then in case an error didn't actually occur, the returned
	// value would be `(*facebook.Error)(nil)`, which doesn't equal to nil and will make it look like
	// there's always an error
	if !reflect.ValueOf(resp.Error).IsNil() {
		return resp.Error
	}
	return nil
}

type Error struct {
	Message string `json:"message"`
	Type    string `json:"type"`
	Code    int    `json:"code"`
}

func (e *Error) Error() string {
	return e.Message
}
<commit_msg>Fix timestap conversion in PostResponse<commit_after>package facebook

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strings"
	"time"

	"github.com/deiwin/facebook/model"
)

// API provides access to the Facebook API graph methods
type API interface {
	// GET /me
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/user#read
	Me() (*model.User, error)

	// GET /me/accounts
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/user/accounts#read
	Accounts() (*model.Accounts, error)

	// GET /{page-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/page/
	Page(pageID string) (*model.Page, error)

	// POST /{page-id}/feed
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/page/feed#publish
	PagePublish(pageAccessToken, pageID string, post *model.Post) (*model.PostResponse, error)

	// GET /{post-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/post#read
	Post(pageAccessToken, postID string) (*model.PostResponse, error)

	// POST /{post-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/post#updating
	PostUpdate(pageAccessToken, postID string, post *model.Post) error

	// DELETE /{post-id}
	//
	// https://developers.facebook.com/docs/graph-api/reference/v2.4/post#deleting
	PostDelete(pageAccessToken, postID string) error
}

type api struct {
	*http.Client
	conf apiConf
}

func (a api) Me() (*model.User, error) {
	resp, err := a.get("/me", url.Values{})
	if err != nil {
		return nil, err
	}
	var user model.User
	err = json.Unmarshal(resp, &user)
	return &user, err
}

func (a api) Accounts() (*model.Accounts, error) {
	resp, err := a.get("/me/accounts", url.Values{})
	if err != nil {
		return nil, err
	}
	var accs model.Accounts
	err = json.Unmarshal(resp, &accs)
	return &accs, err
}

func (a api) Page(pageID string) (*model.Page, error) {
	resp, err := a.get("/"+pageID, url.Values{})
	if err != nil {
		return nil, err
	}
	var page model.Page
	err = json.Unmarshal(resp, &page)
	return &page, err
}

func (a api) PagePublish(pageAccessToken, pageID string, post *model.Post) (*model.PostResponse, error) {
	resp, err := a.postFormable(fmt.Sprintf("/%s/feed", pageID), url.Values{
		"access_token": {pageAccessToken},
	}, post)
	if err != nil {
		return nil, err
	}
	var respPost model.PostResponse
	err = json.Unmarshal(resp, &respPost)
	return &respPost, err
}

func (a api) Post(pageAccessToken, postID string) (*model.PostResponse, error) {
	fieldsToInclude := getJsonTagsForType(reflect.TypeOf(model.PostResponse{}))
	resp, err := a.getFields("/"+postID, url.Values{
		"access_token": {pageAccessToken},
	}, fieldsToInclude)
	if err != nil {
		return nil, err
	}
	// Wrap the actual PostResponse object to do necessary conversions
	var post struct {
		model.PostResponse
		ScheduledPublishTime int64 `json:"scheduled_publish_time"`
	}
	err = json.Unmarshal(resp, &post)
	postResponse := post.PostResponse
	if post.ScheduledPublishTime != 0 {
		postResponse.ScheduledPublishTime = time.Unix(post.ScheduledPublishTime, 0)
	}
	return &postResponse, err
}

func (a api) PostUpdate(pageAccessToken, postID string, post *model.Post) error {
	_, err := a.postFormable("/"+postID, url.Values{
		"access_token": {pageAccessToken},
	}, post)
	return err
}

func (a api) PostDelete(pageAccessToken, postID string) error {
	return a.delete(postID, url.Values{
		"access_token": {pageAccessToken},
	})
}

func getJsonTagsForType(t reflect.Type) []string {
	tags := make([]string, t.NumField())
	for i := 0; i < t.NumField(); i++ {
		tags[i] = t.Field(i).Tag.Get("json")
	}
	return tags
}

func (a api) delete(path string, data url.Values) error {
	url := fmt.Sprintf("%s/%s?%s", a.conf.graphURL, path, data.Encode())
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	if _, err = a.Do(req); err != nil {
		return err
	}
	return nil
}

func (a api) get(path string, data url.Values) ([]byte, error) {
	url := fmt.Sprintf("%s/%s?%s", a.conf.graphURL, path, data.Encode())
	resp, err := a.Get(url)
	return parseResponse(resp, err)
}

func (a api) getFields(path string, data url.Values, fields []string) ([]byte, error) {
	url := fmt.Sprintf("%s/%s?%s&fields=%s", a.conf.graphURL, path, data.Encode(), strings.Join(fields, ","))
	resp, err := a.Get(url)
	return parseResponse(resp, err)
}

func (a api) post(path string, data url.Values) ([]byte, error) {
	resp, err := a.PostForm(a.conf.graphURL+path, data)
	return parseResponse(resp, err)
}

func (a api) postFormable(path string, data url.Values, formable model.Formable) ([]byte, error) {
	form := formable.AsForm()
	for k, vs := range data {
		for _, v := range vs {
			form.Add(k, v)
		}
	}
	resp, err := a.PostForm(a.conf.graphURL+path, form)
	return parseResponse(resp, err)
}

func parseResponse(resp *http.Response, err error) ([]byte, error) {
	if err != nil {
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
		return nil, err
	}
	defer resp.Body.Close()
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return bytes, err
	}
	return bytes, parseError(bytes)
}

func parseError(bytes []byte) error {
	var resp struct {
		Error *Error `json:"error,omitempty"`
	}
	err := json.Unmarshal(bytes, &resp)
	if err != nil {
		return err
	}
	// If we were to just return resp.Error, then in case an error didn't actually occur, the returned
	// value would be `(*facebook.Error)(nil)`, which doesn't equal to nil and will make it look like
	// there's always an error
	if !reflect.ValueOf(resp.Error).IsNil() {
		return resp.Error
	}
	return nil
}

type Error struct {
	Message string `json:"message"`
	Type    string `json:"type"`
	Code    int    `json:"code"`
}

func (e *Error) Error() string {
	return e.Message
}
<|endoftext|>


<commit_before>package simplyput

// TODO: PropertyList can support nested objects with named properties like "A.B.C", support nested JSON objects.
// TODO: Add rudimentary single-property queries, pagination, sorting, etc.
// TODO: Add memcache
// TODO: Support ETags, If-Modified-Since, etc. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)
// TODO: PUT requests
// TODO: HEAD requests
// TODO: PATCH requests/semantics
// TODO: Batch requests (via multipart?)
// TODO: User POSTs a JSON schema, future requests are validated against that schema. Would anybody use that?

import (
	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	idKey        = "_id"
	createdKey   = "_created"
	updatedKey   = "_updated"
	defaultLimit = 10
)

func init() {
	http.HandleFunc("/datastore/v1dev/objects/", handle)
}

type userQuery struct {
	Limit, Offset int
	FilterKey, FilterType, FilterValue,
	StartCursor, EndCursor string
}

// getUserID gets the Google User ID for an access token.
func getUserID(accessToken string, client http.Client) (string, error) {
	resp, err := client.Get("https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + accessToken)
	if err != nil {
		return "", err
	}
	var info struct {
		ID string
	}
	if err = json.NewDecoder(resp.Body).Decode(&info); err != nil {
		return "", err
	}
	resp.Body.Close()
	id := info.ID
	if id == "" {
		return "", errors.New("invalid auth")
	}
	return id, nil
}

// getKindAndID parses the kind and ID from a request path.
func getKindAndID(path string) (string, int64, error) {
	if match, err := regexp.MatchString("/datastore/v1dev/objects/[a-zA-Z]+/[0-9]+", path); err != nil {
		return "", int64(0), err
	} else if match {
		kind := path[len("/datastore/v1dev/objects/"):strings.LastIndex(path, "/")]
		idStr := path[strings.LastIndex(path, "/")+1:]
		id, err := strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			return "", int64(0), err
		}
		return kind, id, nil
	}
	if match, err := regexp.MatchString("/datastore/v1dev/objects/[a-zA-Z]+", path); err != nil {
		return "", int64(0), err
	} else if match {
		kind := path[len("/datastore/v1dev/objects/"):]
		return kind, int64(0), nil
	}
	return "", int64(0), errors.New("invalid path")
}

// handle dispatches requests to the relevant API method and arranges certain common state
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	w.Header().Add("Access-Control-Allow-Origin", "*")

	r.ParseForm()
	client := urlfetch.Client(c)

	// Get the access_token from the request and turn it into a user ID with which we will namespace Kinds in the datastore.
	accessToken := r.Form.Get("access_token")
	if accessToken == "" {
		h := r.Header.Get("Authorization")
		if strings.HasPrefix(h, "Bearer ") {
			accessToken = h[len("Bearer "):]
		}
	}
	if accessToken == "" {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}
	userID, err := getUserID(accessToken, *client)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	kind, id, err := getKindAndID(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	dsKind := fmt.Sprintf("%s--%s", userID, kind)

	resp := make(map[string]interface{}, 0)
	errCode := http.StatusOK
	if id == int64(0) {
		switch r.Method {
		case "POST":
			resp, errCode = insert(c, dsKind, r.Body)
			r.Body.Close()
		case "GET":
			resp, errCode = list(c, dsKind, newUserQuery(r))
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	} else {
		switch r.Method {
		case "GET":
			resp, errCode = get(c, dsKind, id)
		case "DELETE":
			errCode = delete(c, dsKind, id)
		case "POST":
			// This is strictly "replace all properties/values", not "add new properties, update existing"
			resp, errCode = update(c, dsKind, id, r.Body)
			r.Body.Close()
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	}
	if errCode != http.StatusOK {
		http.Error(w, "", errCode)
		return
	}
	if err := json.NewEncoder(w).Encode(&resp); err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
	w.Header().Add("Content-Type", "application/json")
}

func newUserQuery(r *http.Request) userQuery {
	uq := userQuery{
		StartCursor: r.FormValue("start"),
		EndCursor:   r.FormValue("end"),
	}
	// TODO: MustParse for limit/offset, else panic
	uq.Limit, _ = strconv.Atoi(r.FormValue("limit"))
	uq.Offset, _ = strconv.Atoi(r.FormValue("offset"))

	// TODO: Support ?where=foo<bar queries (which may or may not be annoying to scope for users...)
	_ = r.FormValue("where")
	return uq
}

func delete(c appengine.Context, kind string, id int64) int {
	k := datastore.NewKey(c, kind, "", id, nil)
	if err := datastore.Delete(c, k); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return http.StatusNotFound
		} else {
			c.Errorf("%v", err)
			return http.StatusInternalServerError
		}
	}
	return http.StatusOK
}

func get(c appengine.Context, kind string, id int64) (map[string]interface{}, int) {
	k := datastore.NewKey(c, kind, "", id, nil)
	var pl plist
	if err := datastore.Get(c, k, &pl); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return nil, http.StatusNotFound
		}
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m := plistToMap(pl, k)
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

func insert(c appengine.Context, kind string, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[createdKey] = time.Now().Unix()

	pl := mapToPlist(m)

	k := datastore.NewIncompleteKey(c, kind, nil)
	k, err := datastore.Put(c, k, &pl)
	if err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

type prop struct {
	Name string
	Value interface{}
	Multiple bool
	NoIndex bool
}
type plist []prop

// plistToMap transforms a plist such as you would get from the datastore into a map[string]interface{} suitable for JSON-encoding.
func plistToMap(pl plist, k *datastore.Key) map[string]interface{} {
	m := make(map[string]interface{})
	for _, p := range pl {
		if _, exists := m[p.Name]; exists {
			if _, isArr := m[p.Name].([]interface{}); isArr {
				m[p.Name] = append(m[p.Name].([]interface{}), p.Value)
			} else {
				m[p.Name] = []interface{}{m[p.Name], p.Value}
			}
		} else {
			m[p.Name] = p.Value
		}
	}
	m[idKey] = k.IntID()
	return m
}

// mapToPlist transforms a map[string]interface{} such as you would get from decoding JSON into a plist to store in the datastore.
func mapToPlist(m map[string]interface{}) plist {
	pl := make(plist, 0, len(m))
	for k, v := range m {
		if _, mult := v.([]interface{}); mult {
			for _, mv := range v.([]interface{}) {
				pl = append(pl, prop{
					Name:     k,
					Value:    mv,
					Multiple: true,
				})
			}
		} else {
			pl = append(pl, prop{
				Name:  k,
				Value: v,
			})
		}
	}
	return pl
}

func list(c appengine.Context, kind string, uq userQuery) (map[string]interface{}, int) {
	q := datastore.NewQuery(kind)

	if uq.Limit != 0 {
		q = q.Limit(uq.Limit)
	}
	if c, err := datastore.DecodeCursor(uq.StartCursor); err == nil {
		q.Start(c)
	}
	if c, err := datastore.DecodeCursor(uq.EndCursor); err == nil {
		q.End(c)
	}

	items := make([]map[string]interface{}, 0)

	var crs datastore.Cursor
	for t := q.Run(c); ; {
		var pl plist
		k, err := t.Next(&pl)
		if err == datastore.Done {
			break
		}
		if err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
		m := plistToMap(pl, k)
		items = append(items, m)
		if crs, err = t.Cursor(); err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
	}
	r := map[string]interface{}{
		"items":          items,
		"nextStartToken": crs.String(),
	}
	return r, http.StatusOK
}

func update(c appengine.Context, kind string, id int64, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[updatedKey] = time.Now().Unix()

	pl := mapToPlist(m)

	k := datastore.NewKey(c, kind, "", id, nil)
	if _, err := datastore.Put(c, k, &pl); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = id
	return m, http.StatusOK
}
<commit_msg>Refactor plistToMap to take the int64 key, so tests don't depend on datastore.Key<commit_after>package simplyput

// TODO: PropertyList can support nested objects with named properties like "A.B.C", support nested JSON objects.
// TODO: Add rudimentary single-property queries, pagination, sorting, etc.
// TODO: Add memcache
// TODO: Support ETags, If-Modified-Since, etc. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)
// TODO: PUT requests
// TODO: HEAD requests
// TODO: PATCH requests/semantics
// TODO: Batch requests (via multipart?)
// TODO: User POSTs a JSON schema, future requests are validated against that schema. Would anybody use that?

import (
	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	idKey        = "_id"
	createdKey   = "_created"
	updatedKey   = "_updated"
	defaultLimit = 10
)

func init() {
	http.HandleFunc("/datastore/v1dev/objects/", handle)
}

type userQuery struct {
	Limit, Offset int
	FilterKey, FilterType, FilterValue,
	StartCursor, EndCursor string
}

// getUserID gets the Google User ID for an access token.
func getUserID(accessToken string, client http.Client) (string, error) {
	resp, err := client.Get("https://www.googleapis.com/oauth2/v1/userinfo?access_token=" + accessToken)
	if err != nil {
		return "", err
	}
	var info struct {
		ID string
	}
	if err = json.NewDecoder(resp.Body).Decode(&info); err != nil {
		return "", err
	}
	resp.Body.Close()
	id := info.ID
	if id == "" {
		return "", errors.New("invalid auth")
	}
	return id, nil
}

// getKindAndID parses the kind and ID from a request path.
func getKindAndID(path string) (string, int64, error) {
	if match, err := regexp.MatchString("/datastore/v1dev/objects/[a-zA-Z]+/[0-9]+", path); err != nil {
		return "", int64(0), err
	} else if match {
		kind := path[len("/datastore/v1dev/objects/"):strings.LastIndex(path, "/")]
		idStr := path[strings.LastIndex(path, "/")+1:]
		id, err := strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			return "", int64(0), err
		}
		return kind, id, nil
	}
	if match, err := regexp.MatchString("/datastore/v1dev/objects/[a-zA-Z]+", path); err != nil {
		return "", int64(0), err
	} else if match {
		kind := path[len("/datastore/v1dev/objects/"):]
		return kind, int64(0), nil
	}
	return "", int64(0), errors.New("invalid path")
}

// handle dispatches requests to the relevant API method and arranges certain common state
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	w.Header().Add("Access-Control-Allow-Origin", "*")

	r.ParseForm()
	client := urlfetch.Client(c)

	// Get the access_token from the request and turn it into a user ID with which we will namespace Kinds in the datastore.
	accessToken := r.Form.Get("access_token")
	if accessToken == "" {
		h := r.Header.Get("Authorization")
		if strings.HasPrefix(h, "Bearer ") {
			accessToken = h[len("Bearer "):]
		}
	}
	if accessToken == "" {
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}
	userID, err := getUserID(accessToken, *client)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	kind, id, err := getKindAndID(r.URL.Path)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	dsKind := fmt.Sprintf("%s--%s", userID, kind)

	resp := make(map[string]interface{}, 0)
	errCode := http.StatusOK
	if id == int64(0) {
		switch r.Method {
		case "POST":
			resp, errCode = insert(c, dsKind, r.Body)
			r.Body.Close()
		case "GET":
			resp, errCode = list(c, dsKind, newUserQuery(r))
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	} else {
		switch r.Method {
		case "GET":
			resp, errCode = get(c, dsKind, id)
		case "DELETE":
			errCode = delete(c, dsKind, id)
		case "POST":
			// This is strictly "replace all properties/values", not "add new properties, update existing"
			resp, errCode = update(c, dsKind, id, r.Body)
			r.Body.Close()
		default:
			http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
			return
		}
	}
	if errCode != http.StatusOK {
		http.Error(w, "", errCode)
		return
	}
	if err := json.NewEncoder(w).Encode(&resp); err != nil {
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}
	w.Header().Add("Content-Type", "application/json")
}

func newUserQuery(r *http.Request) userQuery {
	uq := userQuery{
		StartCursor: r.FormValue("start"),
		EndCursor:   r.FormValue("end"),
	}
	// TODO: MustParse for limit/offset, else panic
	uq.Limit, _ = strconv.Atoi(r.FormValue("limit"))
	uq.Offset, _ = strconv.Atoi(r.FormValue("offset"))

	// TODO: Support ?where=foo<bar queries (which may or may not be annoying to scope for users...)
	_ = r.FormValue("where")
	return uq
}

func delete(c appengine.Context, kind string, id int64) int {
	k := datastore.NewKey(c, kind, "", id, nil)
	if err := datastore.Delete(c, k); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return http.StatusNotFound
		} else {
			c.Errorf("%v", err)
			return http.StatusInternalServerError
		}
	}
	return http.StatusOK
}

func get(c appengine.Context, kind string, id int64) (map[string]interface{}, int) {
	k := datastore.NewKey(c, kind, "", id, nil)
	var pl plist
	if err := datastore.Get(c, k, &pl); err != nil {
		if err == datastore.ErrNoSuchEntity {
			return nil, http.StatusNotFound
		}
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m := plistToMap(pl, k.IntID())
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

func insert(c appengine.Context, kind string, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[createdKey] = time.Now().Unix()

	pl := mapToPlist(m)

	k := datastore.NewIncompleteKey(c, kind, nil)
	k, err := datastore.Put(c, k, &pl)
	if err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = k.IntID()
	return m, http.StatusOK
}

type prop struct {
	Name string
	Value interface{}
	Multiple bool
	NoIndex bool
}
type plist []prop

// plistToMap transforms a plist such as you would get from the datastore into a map[string]interface{} suitable for JSON-encoding.
func plistToMap(pl plist, id int64) map[string]interface{} {
	m := make(map[string]interface{})
	for _, p := range pl {
		if _, exists := m[p.Name]; exists {
			if _, isArr := m[p.Name].([]interface{}); isArr {
				m[p.Name] = append(m[p.Name].([]interface{}), p.Value)
			} else {
				m[p.Name] = []interface{}{m[p.Name], p.Value}
			}
		} else {
			m[p.Name] = p.Value
		}
	}
	m[idKey] = id
	return m
}

// mapToPlist transforms a map[string]interface{} such as you would get from decoding JSON into a plist to store in the datastore.
func mapToPlist(m map[string]interface{}) plist {
	pl := make(plist, 0, len(m))
	for k, v := range m {
		if _, mult := v.([]interface{}); mult {
			for _, mv := range v.([]interface{}) {
				pl = append(pl, prop{
					Name:     k,
					Value:    mv,
					Multiple: true,
				})
			}
		} else {
			pl = append(pl, prop{
				Name:  k,
				Value: v,
			})
		}
	}
	return pl
}

func list(c appengine.Context, kind string, uq userQuery) (map[string]interface{}, int) {
	q := datastore.NewQuery(kind)

	if uq.Limit != 0 {
		q = q.Limit(uq.Limit)
	}
	if c, err := datastore.DecodeCursor(uq.StartCursor); err == nil {
		q.Start(c)
	}
	if c, err := datastore.DecodeCursor(uq.EndCursor); err == nil {
		q.End(c)
	}

	items := make([]map[string]interface{}, 0)

	var crs datastore.Cursor
	for t := q.Run(c); ; {
		var pl plist
		k, err := t.Next(&pl)
		if err == datastore.Done {
			break
		}
		if err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
		m := plistToMap(pl, k.IntID())
		items = append(items, m)
		if crs, err = t.Cursor(); err != nil {
			c.Errorf("%v", err)
			return nil, http.StatusInternalServerError
		}
	}
	r := map[string]interface{}{
		"items":          items,
		"nextStartToken": crs.String(),
	}
	return r, http.StatusOK
}

func update(c appengine.Context, kind string, id int64, r io.Reader) (map[string]interface{}, int) {
	var m map[string]interface{}
	if err := json.NewDecoder(r).Decode(&m); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[updatedKey] = time.Now().Unix()

	pl := mapToPlist(m)

	k := datastore.NewKey(c, kind, "", id, nil)
	if _, err := datastore.Put(c, k, &pl); err != nil {
		c.Errorf("%v", err)
		return nil, http.StatusInternalServerError
	}
	m[idKey] = id
	return m, http.StatusOK
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"io/ioutil"
	"os"
	"time"
)

// App is the main structure of a cli application. It is recomended that
// and app be created with the cli.NewApp() function
type App struct {
	// The name of the program. Defaults to os.Args[0]
	Name string
	// Description of the program.
	Usage string
	// Version of the program
	Version string
	// List of commands to execute
	Commands []Command
	// List of flags to parse
	Flags []Flag
	// The action to execute when no subcommands are specified
	Action func(context *Context)
	// Compilation date
	Compiled time.Time
	// Author
	Author string
	// Author e-mail
	Email string
}

// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
	info, err := os.Stat(os.Args[0])
	if err != nil {
		return time.Now()
	}
	return info.ModTime()
}

// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
	return &App{
		Name:     os.Args[0],
		Usage:    "A new cli application",
		Version:  "0.0.0",
		Action:   helpCommand.Action,
		Compiled: compileTime(),
		Author:   "Author",
		Email:    "unknown@email",
	}
}

// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) error {
	// append help to commands
	if a.Command(helpCommand.Name) == nil {
		a.Commands = append(a.Commands, helpCommand)
	}

	//append version/help flags
	a.appendFlag(BoolFlag{"version, v", "print the version"})
	a.appendFlag(BoolFlag{"help, h", "show help"})

	// parse flags
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err := set.Parse(arguments[1:])
	nerr := normalizeFlags(a.Flags, set)
	if nerr != nil {
		fmt.Println(nerr)
		context := NewContext(a, set, set)
		ShowAppHelp(context)
		fmt.Println("")
		return nerr
	}
	context := NewContext(a, set, set)

	if err != nil {
		fmt.Printf("Incorrect Usage.\n\n")
		ShowAppHelp(context)
		fmt.Println("")
		return err
	}

	if checkHelp(context) {
		return nil
	}

	if checkVersion(context) {
		return nil
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	// Run default Action
	a.Action(context)
	return nil
}

// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
	for _, c := range a.Commands {
		if c.HasName(name) {
			return &c
		}
	}

	return nil
}

func (a *App) hasFlag(flag Flag) bool {
	for _, f := range a.Flags {
		if flag == f {
			return true
		}
	}

	return false
}

func (a *App) appendFlag(flag Flag) {
	if !a.hasFlag(flag) {
		a.Flags = append(a.Flags, flag)
	}
}
<commit_msg>Add before function to app<commit_after>package cli

import (
	"fmt"
	"io/ioutil"
	"os"
	"time"
)

// App is the main structure of a cli application. It is recomended that
// and app be created with the cli.NewApp() function
type App struct {
	// The name of the program. Defaults to os.Args[0]
	Name string
	// Description of the program.
	Usage string
	// Version of the program
	Version string
	// List of commands to execute
	Commands []Command
	// List of flags to parse
	Flags []Flag
	// An action to execute before any subcommands are run, but after the context is ready
	// If a non-nil error is returned, no subcommands are run
	Before func(context *Context) error
	// The action to execute when no subcommands are specified
	Action func(context *Context)
	// Compilation date
	Compiled time.Time
	// Author
	Author string
	// Author e-mail
	Email string
}

// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
	info, err := os.Stat(os.Args[0])
	if err != nil {
		return time.Now()
	}
	return info.ModTime()
}

// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
	return &App{
		Name:     os.Args[0],
		Usage:    "A new cli application",
		Version:  "0.0.0",
		Action:   helpCommand.Action,
		Compiled: compileTime(),
		Author:   "Author",
		Email:    "unknown@email",
	}
}

// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) error {
	// append help to commands
	if a.Command(helpCommand.Name) == nil {
		a.Commands = append(a.Commands, helpCommand)
	}

	//append version/help flags
	a.appendFlag(BoolFlag{"version, v", "print the version"})
	a.appendFlag(BoolFlag{"help, h", "show help"})

	// parse flags
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err := set.Parse(arguments[1:])
	nerr := normalizeFlags(a.Flags, set)
	if nerr != nil {
		fmt.Println(nerr)
		context := NewContext(a, set, set)
		ShowAppHelp(context)
		fmt.Println("")
		return nerr
	}
	context := NewContext(a, set, set)

	if err != nil {
		fmt.Printf("Incorrect Usage.\n\n")
		ShowAppHelp(context)
		fmt.Println("")
		return err
	}

	if checkHelp(context) {
		return nil
	}

	if checkVersion(context) {
		return nil
	}

	if a.Before != nil {
		err := a.Before(context)
		if err != nil {
			return err
		}
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	// Run default Action
	a.Action(context)
	return nil
}

// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
	for _, c := range a.Commands {
		if c.HasName(name) {
			return &c
		}
	}

	return nil
}

func (a *App) hasFlag(flag Flag) bool {
	for _, f := range a.Flags {
		if flag == f {
			return true
		}
	}

	return false
}

func (a *App) appendFlag(flag Flag) {
	if !a.hasFlag(flag) {
		a.Flags = append(a.Flags, flag)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os/exec"
	"strings"

	yaml "gopkg.in/yaml.v2"

	"net/http"

	"encoding/json"

	"bytes"
	"os"

	"github.com/alexellis/faas/gateway/requests"
)

func main() {
	// var handler string
	var handler string
	var image string

	var action string
	var functionName string
	var gateway string
	var fprocess string
	var language string
	var replace bool
	var nocache bool
	var yamlFile string

	flag.StringVar(&handler, "handler", "", "handler for function, i.e. handler.js")
	flag.StringVar(&image, "image", "", "Docker image name to build")
	flag.StringVar(&action, "action", "", "either build or deploy")
	flag.StringVar(&functionName, "name", "", "give the name of your deployed function")
	flag.StringVar(&gateway, "gateway", "http://localhost:8080", "gateway URI - i.e. http://localhost:8080")
	flag.StringVar(&fprocess, "fprocess", "", "fprocess to be run by the watchdog")
	flag.StringVar(&language, "lang", "node", "programming language template, default is: node")
	flag.BoolVar(&replace, "replace", true, "replace any existing function")
	flag.BoolVar(&nocache, "no-cache", false, "do not use Docker's build cache")

	flag.StringVar(&yamlFile, "yaml", "", "use a yaml file for a set of functions")

	flag.Parse()

	var services Services
	if len(yamlFile) > 0 {
		fileData, err := ioutil.ReadFile(yamlFile)
		if err != nil {
			fmt.Printf("Error: %s\n", err.Error())
			return
		}
		err = yaml.Unmarshal(fileData, &services)
		if err != nil {
			fmt.Printf("Error with YAML file: %s\n", err.Error())
			return
		}

		if services.Provider.Name != "faas" {
			fmt.Println("'faas' is the only valid provider for the faas-cli.")
			return
		}

	}

	if len(action) == 0 {
		fmt.Println("give either -action= build or deploy")
		return
	}

	if action == "build" {
		if len(services.Functions) > 0 {
			for k, function := range services.Functions {
				function.Name = k
				// fmt.Println(k, function)
				fmt.Printf("Building: %s.\n", function.Name)
				buildImage(function.Image, function.Handler, function.Name, function.Language, nocache)
			}
		} else {
			if len(image) == 0 {
				fmt.Println("Give a valid -image name for your Docker image.")
				return
			}
			if len(handler) == 0 {
				fmt.Println("Please give the full path to your function's handler.")
				return
			}
			if len(functionName) == 0 {
				fmt.Println("Please give the deployed -name of your function")
				return
			}
			buildImage(image, handler, functionName, language, nocache)
		}
	} else if action == "deploy" {
		if len(services.Functions) > 0 {
			for k, function := range services.Functions {
				function.Name = k
				// fmt.Println(k, function)
				fmt.Printf("Deploying: %s.\n", function.Name)

				deployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.Language, replace, function.Environment)
			}
		} else {
			if len(image) == 0 {
				fmt.Println("Give an image name to be deployed.")
				return
			}
			if len(functionName) == 0 {
				fmt.Println("Give a -name for your function as it will be deployed on FaaS")
				return
			}
			var envs map[string]string
			deployFunction(fprocess, gateway, functionName, image, language, replace, envs)
		}
	}
}

func deployFunction(fprocess string, gateway string, functionName string, image string, language string, replace bool, envVars map[string]string) {

	// Need to alter Gateway to allow nil/empty string as fprocess, to avoid this repetition.
	fprocessTemplate := "node index.js"
	if len(fprocess) > 0 {
		fprocessTemplate = fprocess
	}
	if language == "python" {
		fprocessTemplate = "python index.py"
	}

	if replace {
		deleteFunction(gateway, functionName)
	}

	req := requests.CreateFunctionRequest{
		EnvProcess: fprocessTemplate,
		Image:      image,
		Network:    "func_functions",
		Service:    functionName,
		EnvVars:    envVars,
	}

	reqBytes, _ := json.Marshal(&req)
	reader := bytes.NewReader(reqBytes)
	res, err := http.Post(gateway+"/system/functions", "application/json", reader)
	if err != nil {
		fmt.Println("Is FaaS deployed? Do you need to specify the -gateway flag?")
		fmt.Println(err)
		return
	}

	fmt.Println(res.Status)
	deployedURL := fmt.Sprintf("URL: %s/function/%s\n", gateway, functionName)
	fmt.Println(deployedURL)
}

func deleteFunction(gateway string, functionName string) {
	delReq := requests.DeleteFunctionRequest{FunctionName: functionName}
	reqBytes, _ := json.Marshal(&delReq)
	reader := bytes.NewReader(reqBytes)

	c := http.Client{}
	req, _ := http.NewRequest("DELETE", gateway+"/system/functions", reader)
	req.Header.Set("Content-Type", "application/json")
	delRes, delErr := c.Do(req)

	if delErr != nil {
		fmt.Println(delErr.Error())
	}
	switch delRes.StatusCode {
	case 200:
		fmt.Println("Removing old service.")
	case 404:
		fmt.Println("No existing service to remove")
	}
}

func buildImage(image string, handler string, functionName string, language string, nocache bool) {

	switch language {
	case "node", "python":
		tempPath := createBuildTemplate(functionName, handler, language)

		fmt.Printf("Building: %s with Docker. Please wait..\n", image)

		cacheFlag := ""
		if nocache {
			cacheFlag = " --no-cache"
		}

		builder := strings.Split(fmt.Sprintf("docker build %s-t %s .", cacheFlag, image), " ")
		if len(os.Getenv("http_proxy")) > 0 || len(os.Getenv("http_proxy")) > 0 {
			builder = strings.Split(fmt.Sprintf("docker build %s--build-arg http_proxy=%s --build-arg https_proxy=%s -t %s .", cacheFlag, os.Getenv("http_proxy"), os.Getenv("https_proxy"), image), " ")
		}

		fmt.Println(strings.Join(builder, " "))
		execBuild(tempPath, builder)
	default:
		log.Fatalf("Language template: %s not supported. Build a custom Dockerfile instead.", language)
	}

	fmt.Printf("Image: %s built.\n", image)
}

// createBuildTemplate creates temporary build folder to perform a Docker build with Node template
func createBuildTemplate(functionName string, handler string, language string) string {
	tempPath := fmt.Sprintf("./build/%s/", functionName)
	fmt.Printf("Clearing temporary folder: %s\n", tempPath)

	clearErr := os.RemoveAll(tempPath)
	if clearErr != nil {
		fmt.Printf("Error clearing down temporary build folder %s\n", tempPath)
	}

	fmt.Printf("Preparing %s %s\n", handler+"/", tempPath+"function")

	functionPath := tempPath + "/function"
	mkdirErr := os.MkdirAll(functionPath, 0700)
	if mkdirErr != nil {
		fmt.Printf("Error creating path %s - %s.\n", functionPath, mkdirErr.Error())
	}

	// TODO: index folders and copy everything from template, rather than set folders.
	// Drop in template
	copyFiles("./template/"+language, tempPath)
	copyFiles("./template/"+language+"/function", tempPath+"function/")

	// Overlay in user-function
	copyFiles(handler, tempPath+"function/")

	return tempPath
}

func copyFiles(path string, destination string) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		if file.IsDir() == false {
			cp(path+"/"+file.Name(), destination+file.Name())
		}
	}
}

func cp(src string, destination string) error {
	fmt.Printf("cp - %s %s\n", src, destination)
	memoryBuffer, readErr := ioutil.ReadFile(src)
	if readErr != nil {
		return fmt.Errorf("Error reading source file: %s\n" + readErr.Error())
	}
	writeErr := ioutil.WriteFile(destination, memoryBuffer, 0660)
	if writeErr != nil {
		return fmt.Errorf("Error writing file: %s\n" + writeErr.Error())
	}

	return nil
}

func execBuild(tempPath string, builder []string) {
	targetCmd := exec.Command(builder[0], builder[1:]...)
	targetCmd.Dir = tempPath
	targetCmd.Stdout = os.Stdout
	targetCmd.Stderr = os.Stderr
	targetCmd.Start()
	targetCmd.Wait()
}
<commit_msg>Validate -action flag in switch statement<commit_after>// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os/exec"
	"strings"

	yaml "gopkg.in/yaml.v2"

	"net/http"

	"encoding/json"

	"bytes"
	"os"

	"github.com/alexellis/faas/gateway/requests"
)

func main() {
	// var handler string
	var handler string
	var image string

	var action string
	var functionName string
	var gateway string
	var fprocess string
	var language string
	var replace bool
	var nocache bool
	var yamlFile string

	flag.StringVar(&handler, "handler", "", "handler for function, i.e. handler.js")
	flag.StringVar(&image, "image", "", "Docker image name to build")
	flag.StringVar(&action, "action", "", "either build or deploy")
	flag.StringVar(&functionName, "name", "", "give the name of your deployed function")
	flag.StringVar(&gateway, "gateway", "http://localhost:8080", "gateway URI - i.e. http://localhost:8080")
	flag.StringVar(&fprocess, "fprocess", "", "fprocess to be run by the watchdog")
	flag.StringVar(&language, "lang", "node", "programming language template, default is: node")
	flag.BoolVar(&replace, "replace", true, "replace any existing function")
	flag.BoolVar(&nocache, "no-cache", false, "do not use Docker's build cache")

	flag.StringVar(&yamlFile, "yaml", "", "use a yaml file for a set of functions")

	flag.Parse()

	var services Services
	if len(yamlFile) > 0 {
		fileData, err := ioutil.ReadFile(yamlFile)
		if err != nil {
			fmt.Printf("Error: %s\n", err.Error())
			return
		}
		err = yaml.Unmarshal(fileData, &services)
		if err != nil {
			fmt.Printf("Error with YAML file: %s\n", err.Error())
			return
		}

		if services.Provider.Name != "faas" {
			fmt.Println("'faas' is the only valid provider for the faas-cli.")
			return
		}

	}

	if len(action) == 0 {
		fmt.Println("give either -action= build or deploy")
		return
	}

	switch action {
	case "build":
		if len(services.Functions) > 0 {
			for k, function := range services.Functions {
				function.Name = k
				// fmt.Println(k, function)
				fmt.Printf("Building: %s.\n", function.Name)
				buildImage(function.Image, function.Handler, function.Name, function.Language, nocache)
			}
		} else {
			if len(image) == 0 {
				fmt.Println("Give a valid -image name for your Docker image.")
				return
			}
			if len(handler) == 0 {
				fmt.Println("Please give the full path to your function's handler.")
				return
			}
			if len(functionName) == 0 {
				fmt.Println("Please give the deployed -name of your function")
				return
			}
			buildImage(image, handler, functionName, language, nocache)
		}
		break
	case "deploy":
		if len(services.Functions) > 0 {
			for k, function := range services.Functions {
				function.Name = k
				// fmt.Println(k, function)
				fmt.Printf("Deploying: %s.\n", function.Name)

				deployFunction(function.FProcess, services.Provider.GatewayURL, function.Name, function.Image, function.Language, replace, function.Environment)
			}
		} else {
			if len(image) == 0 {
				fmt.Println("Give an image name to be deployed.")
				return
			}
			if len(functionName) == 0 {
				fmt.Println("Give a -name for your function as it will be deployed on FaaS")
				return
			}
			var envs map[string]string
			deployFunction(fprocess, gateway, functionName, image, language, replace, envs)
		}
		break
	default:
		fmt.Println("-action must be 'build' or 'deploy'.")
		break
	}
}

func deployFunction(fprocess string, gateway string, functionName string, image string, language string, replace bool, envVars map[string]string) {

	// Need to alter Gateway to allow nil/empty string as fprocess, to avoid this repetition.
	fprocessTemplate := "node index.js"
	if len(fprocess) > 0 {
		fprocessTemplate = fprocess
	}
	if language == "python" {
		fprocessTemplate = "python index.py"
	}

	if replace {
		deleteFunction(gateway, functionName)
	}

	req := requests.CreateFunctionRequest{
		EnvProcess: fprocessTemplate,
		Image:      image,
		Network:    "func_functions",
		Service:    functionName,
		EnvVars:    envVars,
	}

	reqBytes, _ := json.Marshal(&req)
	reader := bytes.NewReader(reqBytes)
	res, err := http.Post(gateway+"/system/functions", "application/json", reader)
	if err != nil {
		fmt.Println("Is FaaS deployed? Do you need to specify the -gateway flag?")
		fmt.Println(err)
		return
	}

	fmt.Println(res.Status)
	deployedURL := fmt.Sprintf("URL: %s/function/%s\n", gateway, functionName)
	fmt.Println(deployedURL)
}

func deleteFunction(gateway string, functionName string) {
	delReq := requests.DeleteFunctionRequest{FunctionName: functionName}
	reqBytes, _ := json.Marshal(&delReq)
	reader := bytes.NewReader(reqBytes)

	c := http.Client{}
	req, _ := http.NewRequest("DELETE", gateway+"/system/functions", reader)
	req.Header.Set("Content-Type", "application/json")
	delRes, delErr := c.Do(req)

	if delErr != nil {
		fmt.Println(delErr.Error())
	}
	switch delRes.StatusCode {
	case 200:
		fmt.Println("Removing old service.")
	case 404:
		fmt.Println("No existing service to remove")
	}
}

func buildImage(image string, handler string, functionName string, language string, nocache bool) {

	switch language {
	case "node", "python":
		tempPath := createBuildTemplate(functionName, handler, language)

		fmt.Printf("Building: %s with Docker. Please wait..\n", image)

		cacheFlag := ""
		if nocache {
			cacheFlag = " --no-cache"
		}

		builder := strings.Split(fmt.Sprintf("docker build %s-t %s .", cacheFlag, image), " ")
		if len(os.Getenv("http_proxy")) > 0 || len(os.Getenv("http_proxy")) > 0 {
			builder = strings.Split(fmt.Sprintf("docker build %s--build-arg http_proxy=%s --build-arg https_proxy=%s -t %s .", cacheFlag, os.Getenv("http_proxy"), os.Getenv("https_proxy"), image), " ")
		}

		fmt.Println(strings.Join(builder, " "))
		execBuild(tempPath, builder)
	default:
		log.Fatalf("Language template: %s not supported. Build a custom Dockerfile instead.", language)
	}

	fmt.Printf("Image: %s built.\n", image)
}

// createBuildTemplate creates temporary build folder to perform a Docker build with Node template
func createBuildTemplate(functionName string, handler string, language string) string {
	tempPath := fmt.Sprintf("./build/%s/", functionName)
	fmt.Printf("Clearing temporary folder: %s\n", tempPath)

	clearErr := os.RemoveAll(tempPath)
	if clearErr != nil {
		fmt.Printf("Error clearing down temporary build folder %s\n", tempPath)
	}

	fmt.Printf("Preparing %s %s\n", handler+"/", tempPath+"function")

	functionPath := tempPath + "/function"
	mkdirErr := os.MkdirAll(functionPath, 0700)
	if mkdirErr != nil {
		fmt.Printf("Error creating path %s - %s.\n", functionPath, mkdirErr.Error())
	}

	// TODO: index folders and copy everything from template, rather than set folders.
	// Drop in template
	copyFiles("./template/"+language, tempPath)
	copyFiles("./template/"+language+"/function", tempPath+"function/")

	// Overlay in user-function
	copyFiles(handler, tempPath+"function/")

	return tempPath
}

func copyFiles(path string, destination string) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		if file.IsDir() == false {
			cp(path+"/"+file.Name(), destination+file.Name())
		}
	}
}

func cp(src string, destination string) error {
	fmt.Printf("cp - %s %s\n", src, destination)
	memoryBuffer, readErr := ioutil.ReadFile(src)
	if readErr != nil {
		return fmt.Errorf("Error reading source file: %s\n" + readErr.Error())
	}
	writeErr := ioutil.WriteFile(destination, memoryBuffer, 0660)
	if writeErr != nil {
		return fmt.Errorf("Error writing file: %s\n" + writeErr.Error())
	}

	return nil
}

func execBuild(tempPath string, builder []string) {
	targetCmd := exec.Command(builder[0], builder[1:]...)
	targetCmd.Dir = tempPath
	targetCmd.Stdout = os.Stdout
	targetCmd.Stderr = os.Stderr
	targetCmd.Start()
	targetCmd.Wait()
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "net/http"
    "strconv"
    "time"
    "flag"
)

func handler(w http.ResponseWriter, r *http.Request) {
    params           := r.URL.Query()
    id               := params["id"][0]
    sleep_seconds, _ := strconv.Atoi( params["t"][0] )
    status, _        := strconv.Atoi( params["s"][0] )
    price            := 0
    if params["p"] != nil {
        price, _ = strconv.Atoi( params["p"][0] )
    }

    if sleep_seconds > 0 {
        time.Sleep( time.Second * time.Duration( sleep_seconds ) ) // milliseconds -> seconds
    }

    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, "{\"id\":\"%s\",\"status\":%d,\"price\":%d}", id, status, price)
}

func main() {
    port := flag.Int("port", 8080, "PORT")
    flag.Parse()

    http.HandleFunc("/ad", handler)
    http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}
<commit_msg>compatible with post parameters.<commit_after>package main

import (
    "fmt"
    "net/http"
    "strconv"
    "time"
    "flag"
)

func handler(w http.ResponseWriter, r *http.Request) {
    id               := r.FormValue("id")
    sleep_seconds, _ := strconv.Atoi( r.FormValue("t") )
    status, _        := strconv.Atoi( r.FormValue("s") )
    price            := 0
    if r.FormValue("p") != "" {
        price, _ = strconv.Atoi( r.FormValue("p") )
    }

    if sleep_seconds > 0 {
        time.Sleep( time.Second * time.Duration( sleep_seconds ) ) // milliseconds -> seconds
    }

    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, "{\"id\":\"%s\",\"status\":%d,\"price\":%d}", id, status, price)
}

func main() {
    port := flag.Int("port", 8080, "PORT")
    flag.Parse()

    http.HandleFunc("/ad", handler)
    http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
}
<|endoftext|>


<commit_before>package gcache

import (
	"container/list"
	"time"
)

// Constantly balances between LRU and LFU, to improve the combined result.
type ARC struct {
	baseCache
	items map[interface{}]*arcItem

	part int
	t1   *arcList
	t2   *arcList
	b1   *arcList
	b2   *arcList
}

func newARC(cb *CacheBuilder) *ARC {
	c := &ARC{}
	buildCache(&c.baseCache, cb)

	c.init()
	c.loadGroup.cache = c
	return c
}

func (c *ARC) init() {
	c.items = make(map[interface{}]*arcItem)
	c.t1 = newARCList()
	c.t2 = newARCList()
	c.b1 = newARCList()
	c.b2 = newARCList()
}

func (c *ARC) replace(key interface{}) {
	var old interface{}
	if (c.t1.Len() > 0 && c.b2.Has(key) && c.t1.Len() == c.part) || (c.t1.Len() > c.part) {
		old = c.t1.RemoveTail()
		c.b1.PushFront(old)
	} else if c.t2.l.Len() > 0 {
		old = c.t2.RemoveTail()
		c.b2.PushFront(old)
	} else {
		return
	}
	item, ok := c.items[old]
	if ok {
		delete(c.items, old)
		if c.evictedFunc != nil {
			c.evictedFunc(item.key, item.value)
		}
	}
}

func (c *ARC) Set(key, value interface{}) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	_, err := c.set(key, value)
	return err
}

func (c *ARC) set(key, value interface{}) (interface{}, error) {
	var err error
	if c.serializeFunc != nil {
		value, err = c.serializeFunc(key, value)
		if err != nil {
			return nil, err
		}
	}

	item, ok := c.items[key]
	if ok {
		item.value = value
	} else {
		item = &arcItem{
			key:   key,
			value: value,
		}
		c.items[key] = item
	}

	if c.expiration != nil {
		t := time.Now().Add(*c.expiration)
		item.expiration = &t
	}

	defer func() {
		if c.addedFunc != nil {
			c.addedFunc(key, value)
		}
	}()

	if c.t1.Has(key) || c.t2.Has(key) {
		return item, nil
	}

	if elt := c.b1.Lookup(key); elt != nil {
		c.part = minInt(c.size, c.part+maxInt(c.b2.Len()/c.b1.Len(), 1))
		c.replace(key)
		c.b1.Remove(key, elt)
		c.t2.PushFront(key)
		return item, nil
	}

	if elt := c.b2.Lookup(key); elt != nil {
		c.part = maxInt(0, c.part-maxInt(c.b1.Len()/c.b2.Len(), 1))
		c.replace(key)
		c.b2.Remove(key, elt)
		c.t2.PushFront(key)
		return item, nil
	}

	if c.t1.Len()+c.b1.Len() == c.size {
		if c.t1.Len() < c.size {
			c.b1.RemoveTail()
			c.replace(key)
		} else {
			pop := c.t1.RemoveTail()
			item, ok := c.items[pop]
			if ok {
				delete(c.items, pop)
				if c.evictedFunc != nil {
					c.evictedFunc(item.key, item.value)
				}
			}
		}
	} else {
		total := c.t1.Len() + c.b1.Len() + c.t2.Len() + c.b2.Len()
		if total >= c.size {
			if total == (2 * c.size) {
				c.b2.RemoveTail()
			}
			c.replace(key)
		}
	}
	c.t1.PushFront(key)
	return item, nil
}

// Get a value from cache pool using key if it exists. If not exists and it has LoaderFunc, it will generate the value using you have specified LoaderFunc method returns value.
func (c *ARC) Get(key interface{}) (interface{}, error) {
	v, err := c.get(key, false)
	if err == KeyNotFoundError {
		return c.getWithLoader(key, true)
	}
	return v, err
}

// Get a value from cache pool using key if it exists.
// If it dose not exists key, returns KeyNotFoundError.
// And send a request which refresh value for specified key if cache object has LoaderFunc.
func (c *ARC) GetIFPresent(key interface{}) (interface{}, error) {
	v, err := c.get(key, false)
	if err == KeyNotFoundError {
		return c.getWithLoader(key, false)
	}
	return v, err
}

func (c *ARC) get(key interface{}, onLoad bool) (interface{}, error) {
	v, err := c.getValue(key, onLoad)
	if err != nil {
		return nil, err
	}
	if c.deserializeFunc != nil {
		return c.deserializeFunc(key, v)
	}
	return v, nil
}

func (c *ARC) getValue(key interface{}, onLoad bool) (interface{}, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if elt := c.t1.Lookup(key); elt != nil {
		c.t1.Remove(key, elt)
		item := c.items[key]
		if !item.IsExpired(nil) {
			c.t2.PushFront(key)
			if !onLoad {
				c.stats.IncrHitCount()
			}
			return item.value, nil
		} else {
			delete(c.items, key)
			c.b1.PushFront(key)
			if c.evictedFunc != nil {
				c.evictedFunc(item.key, item.value)
			}
		}
	}
	if elt := c.t2.Lookup(key); elt != nil {
		item := c.items[key]
		if !item.IsExpired(nil) {
			c.t2.MoveToFront(elt)
			if !onLoad {
				c.stats.IncrHitCount()
			}
			return item.value, nil
		} else {
			delete(c.items, key)
			c.t2.Remove(key, elt)
			c.b2.PushFront(key)
			if c.evictedFunc != nil {
				c.evictedFunc(item.key, item.value)
			}
		}
	}

	if !onLoad {
		c.stats.IncrMissCount()
	}
	return nil, KeyNotFoundError
}

func (c *ARC) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
	if c.loaderFunc == nil {
		return nil, KeyNotFoundError
	}
	value, _, err := c.load(key, func(v interface{}, e error) (interface{}, error) {
		if e != nil {
			return nil, e
		}
		c.mu.Lock()
		_, err := c.set(key, v)
		defer c.mu.Unlock()
		if err != nil {
			return nil, err
		}
		return v, nil
	}, isWait)
	if err != nil {
		return nil, err
	}
	return value, nil
}

// Remove removes the provided key from the cache.
func (c *ARC) Remove(key interface{}) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	return c.remove(key)
}

func (c *ARC) remove(key interface{}) bool {
	if elt := c.t1.Lookup(key); elt != nil {
		v := elt.Value.(*arcItem).value
		c.t1.Remove(key, elt)
		if c.evictedFunc != nil {
			c.evictedFunc(key, v)
		}
		return true
	}

	if elt := c.t2.Lookup(key); elt != nil {
		v := elt.Value.(*arcItem).value
		c.t2.Remove(key, elt)
		if c.evictedFunc != nil {
			c.evictedFunc(key, v)
		}
		return true
	}

	return false
}

func (c *ARC) keys() []interface{} {
	c.mu.RLock()
	defer c.mu.RUnlock()
	keys := make([]interface{}, len(c.items))
	var i = 0
	for k := range c.items {
		keys[i] = k
		i++
	}
	return keys
}

// Keys returns a slice of the keys in the cache.
func (c *ARC) Keys() []interface{} {
	keys := []interface{}{}
	for _, k := range c.keys() {
		_, err := c.GetIFPresent(k)
		if err == nil {
			keys = append(keys, k)
		}
	}
	return keys
}

// Returns all key-value pairs in the cache.
func (c *ARC) GetALL() map[interface{}]interface{} {
	m := make(map[interface{}]interface{})
	for _, k := range c.keys() {
		v, err := c.GetIFPresent(k)
		if err == nil {
			m[k] = v
		}
	}
	return m
}

// Len returns the number of items in the cache.
func (c *ARC) Len() int {
	return len(c.GetALL())
}

// Purge is used to completely clear the cache
func (c *ARC) Purge() {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.init()
}

// returns boolean value whether this item is expired or not.
func (it *arcItem) IsExpired(now *time.Time) bool {
	if it.expiration == nil {
		return false
	}
	if now == nil {
		t := time.Now()
		now = &t
	}
	return it.expiration.Before(*now)
}

type arcList struct {
	l    *list.List
	keys map[interface{}]*list.Element
}

type arcItem struct {
	key        interface{}
	value      interface{}
	expiration *time.Time
}

func newARCList() *arcList {
	return &arcList{
		l:    list.New(),
		keys: make(map[interface{}]*list.Element),
	}
}

func (al *arcList) Has(key interface{}) bool {
	_, ok := al.keys[key]
	return ok
}

func (al *arcList) Lookup(key interface{}) *list.Element {
	elt := al.keys[key]
	return elt
}

func (al *arcList) MoveToFront(elt *list.Element) {
	al.l.MoveToFront(elt)
}

func (al *arcList) PushFront(key interface{}) {
	if elt, ok := al.keys[key]; ok {
		al.l.MoveToFront(elt)
		return
	}
	elt := al.l.PushFront(key)
	al.keys[key] = elt
}

func (al *arcList) Remove(key interface{}, elt *list.Element) {
	delete(al.keys, key)
	al.l.Remove(elt)
}

func (al *arcList) RemoveTail() interface{} {
	elt := al.l.Back()
	al.l.Remove(elt)

	key := elt.Value
	delete(al.keys, key)

	return key
}

func (al *arcList) Len() int {
	return al.l.Len()
}
<commit_msg>fix panics on arc remove method<commit_after>package gcache

import (
	"container/list"
	"time"
)

// Constantly balances between LRU and LFU, to improve the combined result.
type ARC struct {
	baseCache
	items map[interface{}]*arcItem

	part int
	t1   *arcList
	t2   *arcList
	b1   *arcList
	b2   *arcList
}

func newARC(cb *CacheBuilder) *ARC {
	c := &ARC{}
	buildCache(&c.baseCache, cb)

	c.init()
	c.loadGroup.cache = c
	return c
}

func (c *ARC) init() {
	c.items = make(map[interface{}]*arcItem)
	c.t1 = newARCList()
	c.t2 = newARCList()
	c.b1 = newARCList()
	c.b2 = newARCList()
}

func (c *ARC) replace(key interface{}) {
	var old interface{}
	if (c.t1.Len() > 0 && c.b2.Has(key) && c.t1.Len() == c.part) || (c.t1.Len() > c.part) {
		old = c.t1.RemoveTail()
		c.b1.PushFront(old)
	} else if c.t2.l.Len() > 0 {
		old = c.t2.RemoveTail()
		c.b2.PushFront(old)
	} else {
		return
	}
	item, ok := c.items[old]
	if ok {
		delete(c.items, old)
		if c.evictedFunc != nil {
			c.evictedFunc(item.key, item.value)
		}
	}
}

func (c *ARC) Set(key, value interface{}) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	_, err := c.set(key, value)
	return err
}

func (c *ARC) set(key, value interface{}) (interface{}, error) {
	var err error
	if c.serializeFunc != nil {
		value, err = c.serializeFunc(key, value)
		if err != nil {
			return nil, err
		}
	}

	item, ok := c.items[key]
	if ok {
		item.value = value
	} else {
		item = &arcItem{
			key:   key,
			value: value,
		}
		c.items[key] = item
	}

	if c.expiration != nil {
		t := time.Now().Add(*c.expiration)
		item.expiration = &t
	}

	defer func() {
		if c.addedFunc != nil {
			c.addedFunc(key, value)
		}
	}()

	if c.t1.Has(key) || c.t2.Has(key) {
		return item, nil
	}

	if elt := c.b1.Lookup(key); elt != nil {
		c.part = minInt(c.size, c.part+maxInt(c.b2.Len()/c.b1.Len(), 1))
		c.replace(key)
		c.b1.Remove(key, elt)
		c.t2.PushFront(key)
		return item, nil
	}

	if elt := c.b2.Lookup(key); elt != nil {
		c.part = maxInt(0, c.part-maxInt(c.b1.Len()/c.b2.Len(), 1))
		c.replace(key)
		c.b2.Remove(key, elt)
		c.t2.PushFront(key)
		return item, nil
	}

	if c.t1.Len()+c.b1.Len() == c.size {
		if c.t1.Len() < c.size {
			c.b1.RemoveTail()
			c.replace(key)
		} else {
			pop := c.t1.RemoveTail()
			item, ok := c.items[pop]
			if ok {
				delete(c.items, pop)
				if c.evictedFunc != nil {
					c.evictedFunc(item.key, item.value)
				}
			}
		}
	} else {
		total := c.t1.Len() + c.b1.Len() + c.t2.Len() + c.b2.Len()
		if total >= c.size {
			if total == (2 * c.size) {
				c.b2.RemoveTail()
			}
			c.replace(key)
		}
	}
	c.t1.PushFront(key)
	return item, nil
}

// Get a value from cache pool using key if it exists. If not exists and it has LoaderFunc, it will generate the value using you have specified LoaderFunc method returns value.
func (c *ARC) Get(key interface{}) (interface{}, error) {
	v, err := c.get(key, false)
	if err == KeyNotFoundError {
		return c.getWithLoader(key, true)
	}
	return v, err
}

// Get a value from cache pool using key if it exists.
// If it dose not exists key, returns KeyNotFoundError.
// And send a request which refresh value for specified key if cache object has LoaderFunc.
func (c *ARC) GetIFPresent(key interface{}) (interface{}, error) {
	v, err := c.get(key, false)
	if err == KeyNotFoundError {
		return c.getWithLoader(key, false)
	}
	return v, err
}

func (c *ARC) get(key interface{}, onLoad bool) (interface{}, error) {
	v, err := c.getValue(key, onLoad)
	if err != nil {
		return nil, err
	}
	if c.deserializeFunc != nil {
		return c.deserializeFunc(key, v)
	}
	return v, nil
}

func (c *ARC) getValue(key interface{}, onLoad bool) (interface{}, error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if elt := c.t1.Lookup(key); elt != nil {
		c.t1.Remove(key, elt)
		item := c.items[key]
		if !item.IsExpired(nil) {
			c.t2.PushFront(key)
			if !onLoad {
				c.stats.IncrHitCount()
			}
			return item.value, nil
		} else {
			delete(c.items, key)
			c.b1.PushFront(key)
			if c.evictedFunc != nil {
				c.evictedFunc(item.key, item.value)
			}
		}
	}
	if elt := c.t2.Lookup(key); elt != nil {
		item := c.items[key]
		if !item.IsExpired(nil) {
			c.t2.MoveToFront(elt)
			if !onLoad {
				c.stats.IncrHitCount()
			}
			return item.value, nil
		} else {
			delete(c.items, key)
			c.t2.Remove(key, elt)
			c.b2.PushFront(key)
			if c.evictedFunc != nil {
				c.evictedFunc(item.key, item.value)
			}
		}
	}

	if !onLoad {
		c.stats.IncrMissCount()
	}
	return nil, KeyNotFoundError
}

func (c *ARC) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
	if c.loaderFunc == nil {
		return nil, KeyNotFoundError
	}
	value, _, err := c.load(key, func(v interface{}, e error) (interface{}, error) {
		if e != nil {
			return nil, e
		}
		c.mu.Lock()
		_, err := c.set(key, v)
		defer c.mu.Unlock()
		if err != nil {
			return nil, err
		}
		return v, nil
	}, isWait)
	if err != nil {
		return nil, err
	}
	return value, nil
}

// Remove removes the provided key from the cache.
func (c *ARC) Remove(key interface{}) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	return c.remove(key)
}

func (c *ARC) remove(key interface{}) bool {
	if elt := c.t1.Lookup(key); elt != nil {
		c.t1.Remove(key, elt)
		item := c.items[key]
		delete(c.items, key)
		c.b1.PushFront(key)
		if c.evictedFunc != nil {
			c.evictedFunc(key, item.value)
		}
		return true
	}

	if elt := c.t2.Lookup(key); elt != nil {
		c.t2.Remove(key, elt)
		item := c.items[key]
		delete(c.items, key)
		c.b2.PushFront(key)
		if c.evictedFunc != nil {
			c.evictedFunc(key, item.value)
		}
		return true
	}

	return false
}

func (c *ARC) keys() []interface{} {
	c.mu.RLock()
	defer c.mu.RUnlock()
	keys := make([]interface{}, len(c.items))
	var i = 0
	for k := range c.items {
		keys[i] = k
		i++
	}
	return keys
}

// Keys returns a slice of the keys in the cache.
func (c *ARC) Keys() []interface{} {
	keys := []interface{}{}
	for _, k := range c.keys() {
		_, err := c.GetIFPresent(k)
		if err == nil {
			keys = append(keys, k)
		}
	}
	return keys
}

// Returns all key-value pairs in the cache.
func (c *ARC) GetALL() map[interface{}]interface{} {
	m := make(map[interface{}]interface{})
	for _, k := range c.keys() {
		v, err := c.GetIFPresent(k)
		if err == nil {
			m[k] = v
		}
	}
	return m
}

// Len returns the number of items in the cache.
func (c *ARC) Len() int {
	return len(c.GetALL())
}

// Purge is used to completely clear the cache
func (c *ARC) Purge() {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.init()
}

// returns boolean value whether this item is expired or not.
func (it *arcItem) IsExpired(now *time.Time) bool {
	if it.expiration == nil {
		return false
	}
	if now == nil {
		t := time.Now()
		now = &t
	}
	return it.expiration.Before(*now)
}

type arcList struct {
	l    *list.List
	keys map[interface{}]*list.Element
}

type arcItem struct {
	key        interface{}
	value      interface{}
	expiration *time.Time
}

func newARCList() *arcList {
	return &arcList{
		l:    list.New(),
		keys: make(map[interface{}]*list.Element),
	}
}

func (al *arcList) Has(key interface{}) bool {
	_, ok := al.keys[key]
	return ok
}

func (al *arcList) Lookup(key interface{}) *list.Element {
	elt := al.keys[key]
	return elt
}

func (al *arcList) MoveToFront(elt *list.Element) {
	al.l.MoveToFront(elt)
}

func (al *arcList) PushFront(key interface{}) {
	if elt, ok := al.keys[key]; ok {
		al.l.MoveToFront(elt)
		return
	}
	elt := al.l.PushFront(key)
	al.keys[key] = elt
}

func (al *arcList) Remove(key interface{}, elt *list.Element) {
	delete(al.keys, key)
	al.l.Remove(elt)
}

func (al *arcList) RemoveTail() interface{} {
	elt := al.l.Back()
	al.l.Remove(elt)

	key := elt.Value
	delete(al.keys, key)

	return key
}

func (al *arcList) Len() int {
	return al.l.Len()
}
<|endoftext|>