<commit_before>package rocserv

import (
	"sync"

	"github.com/shawnfeng/sutil/slog"
)

const (
	defaultPoolLen = 512
)

type ClientPool struct {
	poolClient sync.Map
	poolLen    int
	Factory    func(addr string) rpcClient
}

// NewClientPool constructor of pool, 如果连接数过低，修正为默认值
func NewClientPool(poolLen int, factory func(addr string) rpcClient) *ClientPool {
	if poolLen < defaultPoolLen {
		poolLen = defaultPoolLen
	}
	return &ClientPool{poolLen: poolLen, Factory: factory}
}

// Get get connection from pool, if reach max, create new connection and return
func (m *ClientPool) Get(addr string) rpcClient {
	fun := "ClientPool.Get -->"

	po := m.getPool(addr)
	var c rpcClient
	select {
	case c = <-po:
		slog.Tracef("%s get: %s len:%d", fun, addr, len(po))
	default:
		c = m.Factory(addr)
	}
	return c
}

func (m *ClientPool) getPool(addr string) chan rpcClient {
	fun := "ClientPool.getPool -->"

	var tmp chan rpcClient
	value, ok := m.poolClient.Load(addr)
	if ok == true {
		tmp = value.(chan rpcClient)
	} else {
		slog.Infof("%s not found addr:%s", fun, addr)
		tmp = make(chan rpcClient, m.poolLen)
		m.poolClient.Store(addr, tmp)
	}
	return tmp
}

// Put 连接池回收连接
func (m *ClientPool) Put(addr string, client rpcClient, err error) {
	fun := "ClientPool.Put -->"
	// do nothing，应该不会发生
	if client == nil {
		slog.Errorf("%s put nil rpc client to pool: %s", fun, addr)
		return
	}
	// close client and don't put to pool
	if err != nil {
		slog.Errorf("%s put rpc client to pool: %s, with err: %v", fun, addr, err)
		client.Close()
		return
	}

	// po 链接池
	po := m.getPool(addr)
	select {
	// 回收连接 client
	case po <- client:
		slog.Tracef("%s payback:%s len:%d", fun, addr, len(po))

	//不能回收了，关闭链接(满了)
	default:
		slog.Warnf("%s full not payback: %s len: %d", fun, addr, len(po))
		client.Close()
	}
}
<commit_msg>change log from error to warn when put error<commit_after>package rocserv

import (
	"sync"

	"github.com/shawnfeng/sutil/slog"
)

const (
	defaultPoolLen = 512
)

type ClientPool struct {
	poolClient sync.Map
	poolLen    int
	Factory    func(addr string) rpcClient
}

// NewClientPool constructor of pool, 如果连接数过低，修正为默认值
func NewClientPool(poolLen int, factory func(addr string) rpcClient) *ClientPool {
	if poolLen < defaultPoolLen {
		poolLen = defaultPoolLen
	}
	return &ClientPool{poolLen: poolLen, Factory: factory}
}

// Get get connection from pool, if reach max, create new connection and return
func (m *ClientPool) Get(addr string) rpcClient {
	fun := "ClientPool.Get -->"

	po := m.getPool(addr)
	var c rpcClient
	select {
	case c = <-po:
		slog.Tracef("%s get: %s len:%d", fun, addr, len(po))
	default:
		c = m.Factory(addr)
	}
	return c
}

func (m *ClientPool) getPool(addr string) chan rpcClient {
	fun := "ClientPool.getPool -->"

	var tmp chan rpcClient
	value, ok := m.poolClient.Load(addr)
	if ok == true {
		tmp = value.(chan rpcClient)
	} else {
		slog.Infof("%s not found addr:%s", fun, addr)
		tmp = make(chan rpcClient, m.poolLen)
		m.poolClient.Store(addr, tmp)
	}
	return tmp
}

// Put 连接池回收连接
func (m *ClientPool) Put(addr string, client rpcClient, err error) {
	fun := "ClientPool.Put -->"
	// do nothing，应该不会发生
	if client == nil {
		slog.Errorf("%s put nil rpc client to pool: %s", fun, addr)
		return
	}
	// close client and don't put to pool
	if err != nil {
		slog.Warnf("%s put rpc client to pool: %s, with err: %v", fun, addr, err)
		client.Close()
		return
	}

	// po 链接池
	po := m.getPool(addr)
	select {
	// 回收连接 client
	case po <- client:
		slog.Tracef("%s payback:%s len:%d", fun, addr, len(po))

	//不能回收了，关闭链接(满了)
	default:
		slog.Warnf("%s full not payback: %s len: %d", fun, addr, len(po))
		client.Close()
	}
}
<|endoftext|>


<commit_before>// Package authgateway is used for all out bound calls to authenticate
// a User
package authgateway

import (
	"context"

	"github.com/gilcrest/go-api-basic/domain/auth"

	"github.com/gilcrest/go-api-basic/domain/user"

	"golang.org/x/oauth2"
	googleoauth "google.golang.org/api/oauth2/v2"
	"google.golang.org/api/option"

	"github.com/gilcrest/go-api-basic/domain/errs"
)

// GoogleToken2User is used to convert an auth.AccessToken to a User
// through Google's API
type GoogleToken2User struct{}

// User calls the Google Userinfo API with the access token and converts
// the Userinfo struct to a User struct
func (c GoogleToken2User) User(ctx context.Context, token auth.AccessToken) (*user.User, error) {
	ui, err := userInfo(ctx, token.NewGoogleOauth2Token())
	if err != nil {
		return nil, err
	}

	return newUser(ui), nil
}

// userInfo makes an outbound https call to Google using their
// Oauth2 v2 api and returns a Userinfo struct which has most
// profile data elements you typically need
func userInfo(ctx context.Context, token *oauth2.Token) (*googleoauth.Userinfo, error) {

	oauthService, err := googleoauth.NewService(ctx, option.WithTokenSource(oauth2.StaticTokenSource(token)))
	if err != nil {
		return nil, errs.E(err)
	}

	userInfo, err := oauthService.Userinfo.Get().Do()
	if err != nil {
		// "In summary, a 401 Unauthorized response should be used for missing or
		// bad authentication, and a 403 Forbidden response should be used afterwards,
		// when the user is authenticated but isn’t authorized to perform the
		// requested operation on the given resource."
		// In this case, we are getting a bad response from Google service, assume
		// they are not able to authenticate properly
		return nil, errs.E(errs.Unauthenticated, err)
	}

	return userInfo, nil
}

// newUser initializes the user.User struct given a Userinfo struct
// from Google
func newUser(userinfo *googleoauth.Userinfo) *user.User {
	return &user.User{
		Email:     userinfo.Email,
		LastName:  userinfo.FamilyName,
		FirstName: userinfo.GivenName,
		FullName:  userinfo.Name,
		//Gender:       userinfo.Gender,
		HostedDomain: userinfo.Hd,
		PictureURL:   userinfo.Picture,
		ProfileLink:  userinfo.Link,
	}
}
<commit_msg>better package comment<commit_after>// Package authgateway encapsulates outbound calls to authenticate
// a User
package authgateway

import (
	"context"

	"github.com/gilcrest/go-api-basic/domain/auth"

	"github.com/gilcrest/go-api-basic/domain/user"

	"golang.org/x/oauth2"
	googleoauth "google.golang.org/api/oauth2/v2"
	"google.golang.org/api/option"

	"github.com/gilcrest/go-api-basic/domain/errs"
)

// GoogleToken2User is used to convert an auth.AccessToken to a User
// through Google's API
type GoogleToken2User struct{}

// User calls the Google Userinfo API with the access token and converts
// the Userinfo struct to a User struct
func (c GoogleToken2User) User(ctx context.Context, token auth.AccessToken) (*user.User, error) {
	ui, err := userInfo(ctx, token.NewGoogleOauth2Token())
	if err != nil {
		return nil, err
	}

	return newUser(ui), nil
}

// userInfo makes an outbound https call to Google using their
// Oauth2 v2 api and returns a Userinfo struct which has most
// profile data elements you typically need
func userInfo(ctx context.Context, token *oauth2.Token) (*googleoauth.Userinfo, error) {

	oauthService, err := googleoauth.NewService(ctx, option.WithTokenSource(oauth2.StaticTokenSource(token)))
	if err != nil {
		return nil, errs.E(err)
	}

	userInfo, err := oauthService.Userinfo.Get().Do()
	if err != nil {
		// "In summary, a 401 Unauthorized response should be used for missing or
		// bad authentication, and a 403 Forbidden response should be used afterwards,
		// when the user is authenticated but isn’t authorized to perform the
		// requested operation on the given resource."
		// In this case, we are getting a bad response from Google service, assume
		// they are not able to authenticate properly
		return nil, errs.E(errs.Unauthenticated, err)
	}

	return userInfo, nil
}

// newUser initializes the user.User struct given a Userinfo struct
// from Google
func newUser(userinfo *googleoauth.Userinfo) *user.User {
	return &user.User{
		Email:     userinfo.Email,
		LastName:  userinfo.FamilyName,
		FirstName: userinfo.GivenName,
		FullName:  userinfo.Name,
		//Gender:       userinfo.Gender,
		HostedDomain: userinfo.Hd,
		PictureURL:   userinfo.Picture,
		ProfileLink:  userinfo.Link,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	anko_core "github.com/mattn/anko/builtins"
	"github.com/mattn/anko/vm"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

type Context struct {
	Env        *vm.Env
	Build      *Build
	Properties []string
	Error      error
}

func NewContext(build *Build, object Object) (*Context, error) {
	env := vm.NewEnv()
	anko_core.LoadAllBuiltins(env)
	context := &Context{
		Env:        env,
		Build:      build,
		Properties: object.Fields(),
	}
	err := context.SetProperties(object)
	if err != nil {
		return nil, err
	}
	return context, nil
}

func (context *Context) Evaluate(source string) (interface{}, error) {
	value, err := context.Env.Execute(source)
	return value.Interface(), err
}

func (context *Context) SetProperty(name string, value interface{}) {
	context.Env.Define(name, value)
}

func (context *Context) SetProperties(object Object) error {
	todo, _ := NewObject(object)
	length := len(todo)
	list := make([]string, len(todo)+1)
	var err error
	for length < len(list) && len(todo) > 0 {
		list = todo.Fields()
		for _, field := range list {
			value := todo[field]
			str, ok := value.(string)
			if ok {
				replaced, err := context.ReplaceProperties(str)
				if err != nil {
					continue
				} else {
					context.SetProperty(field, replaced)
					delete(todo, field)
				}
			} else {
				context.SetProperty(field, value)
				delete(todo, field)
			}
		}
		length = len(todo)
	}
	if len(todo) > 0 {
		return err
	}
	return nil
}

func (context *Context) GetProperty(name string) (interface{}, error) {
	value, err := context.Env.Get(name)
	if err != nil {
		return nil, err
	}
	return value.Interface(), nil
}

func (context *Context) replaceProperty(expression string) string {
	name := expression[2 : len(expression)-1]
	value, err := context.GetProperty(name)
	context.Error = err
	str, err := PropertyToString(value, false)
	context.Error = err
	return str
}

func (context *Context) ReplaceProperties(text string) (string, error) {
	r := regexp.MustCompile("#{.*?}")
	replaced := r.ReplaceAllStringFunc(text, context.replaceProperty)
	err := context.Error
	context.Error = nil
	return replaced, err
}

func PropertyToString(object interface{}, quotes bool) (string, error) {
	switch value := object.(type) {
	case bool:
		return strconv.FormatBool(value), nil
	case string:
		if quotes {
			return "\"" + value + "\"", nil
		} else {
			return value, nil
		}
	case int:
		return strconv.Itoa(value), nil
	case int32:
		return strconv.Itoa(int(value)), nil
	case int64:
		return strconv.Itoa(int(value)), nil
	case float64:
		return strconv.FormatFloat(value, 'g', -1, 64), nil
	default:
		switch reflect.TypeOf(object).Kind() {
		case reflect.Slice:
			slice := reflect.ValueOf(object)
			elements := make([]string, slice.Len())
			for index := 0; index < slice.Len(); index++ {
				str, err := PropertyToString(slice.Index(index).Interface(), quotes)
				if err != nil {
					return "", err
				}
				elements[index] = str
			}
			return "[" + strings.Join(elements, ", ") + "]", nil
		case reflect.Map:
			dict := reflect.ValueOf(object)
			elements := make(map[string]string)
			var keys []string
			for _, key := range dict.MapKeys() {
				value := dict.MapIndex(key)
				keyStr, err := PropertyToString(key.Interface(), quotes)
				if err != nil {
					return "", err
				}
				keys = append(keys, keyStr)
				valueStr, err := PropertyToString(value.Interface(), quotes)
				if err != nil {
					return "", err
				}
				elements[keyStr] = valueStr
			}
			sort.Strings(keys)
			pairs := make([]string, len(keys))
			for index, key := range keys {
				pairs[index] = key + ": " + elements[key]
			}
			return "[" + strings.Join(pairs, ", ") + "]", nil
		default:
			return "", fmt.Errorf("no serializer for type '%T'", object)
		}
	}
}
<commit_msg>Fixed properties evaluation<commit_after>package main

import (
	"fmt"
	anko_core "github.com/mattn/anko/builtins"
	"github.com/mattn/anko/vm"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

type Context struct {
	Env        *vm.Env
	Build      *Build
	Properties []string
	Error      error
}

func NewContext(build *Build, object Object) (*Context, error) {
	env := vm.NewEnv()
	anko_core.LoadAllBuiltins(env)
	context := &Context{
		Env:        env,
		Build:      build,
		Properties: object.Fields(),
	}
	err := context.SetProperties(object)
	if err != nil {
		return nil, err
	}
	return context, nil
}

func (context *Context) Evaluate(source string) (interface{}, error) {
	value, err := context.Env.Execute(source)
	return value.Interface(), err
}

func (context *Context) SetProperty(name string, value interface{}) {
	context.Env.Define(name, value)
}

func (context *Context) SetProperties(object Object) error {
	todo := object.Fields()
	var crash error
	for len(todo) > 0 {
		var done []string
		for _, name := range todo {
			value := object[name]
			str, ok := value.(string)
			if ok {
				eval, err := context.ReplaceProperties(str)
				if err == nil {
					context.SetProperty(name, eval)
					done = append(done, name)
				} else {
					crash = err
				}
			} else {
				context.SetProperty(name, value)
				done = append(done, name)
			}
		}
		if len(done) == 0 {
			return fmt.Errorf("evaluating properties: %v", crash)
		}
		var next []string
		for _, name := range todo {
			found := false
			for _, n := range done {
				if name == n {
					found = true
				}
			}
			if !found {
				next = append(next, name)
			}
		}
		todo = next
	}
	return nil
}

func (context *Context) GetProperty(name string) (interface{}, error) {
	value, err := context.Env.Get(name)
	if err != nil {
		return nil, err
	}
	return value.Interface(), nil
}

func (context *Context) replaceProperty(expression string) string {
	name := expression[2 : len(expression)-1]
	value, err := context.GetProperty(name)
	if err != nil {
		context.Error = err
	}
	var str string
	if err == nil {
		str, err = PropertyToString(value, false)
		if err != nil {
			context.Error = err
		}
	}
	return str
}

func (context *Context) ReplaceProperties(text string) (string, error) {
	r := regexp.MustCompile("#{.*?}")
	replaced := r.ReplaceAllStringFunc(text, context.replaceProperty)
	err := context.Error
	context.Error = nil
	return replaced, err
}

func PropertyToString(object interface{}, quotes bool) (string, error) {
	switch value := object.(type) {
	case bool:
		return strconv.FormatBool(value), nil
	case string:
		if quotes {
			return "\"" + value + "\"", nil
		} else {
			return value, nil
		}
	case int:
		return strconv.Itoa(value), nil
	case int32:
		return strconv.Itoa(int(value)), nil
	case int64:
		return strconv.Itoa(int(value)), nil
	case float64:
		return strconv.FormatFloat(value, 'g', -1, 64), nil
	default:
		switch reflect.TypeOf(object).Kind() {
		case reflect.Slice:
			slice := reflect.ValueOf(object)
			elements := make([]string, slice.Len())
			for index := 0; index < slice.Len(); index++ {
				str, err := PropertyToString(slice.Index(index).Interface(), quotes)
				if err != nil {
					return "", err
				}
				elements[index] = str
			}
			return "[" + strings.Join(elements, ", ") + "]", nil
		case reflect.Map:
			dict := reflect.ValueOf(object)
			elements := make(map[string]string)
			var keys []string
			for _, key := range dict.MapKeys() {
				value := dict.MapIndex(key)
				keyStr, err := PropertyToString(key.Interface(), quotes)
				if err != nil {
					return "", err
				}
				keys = append(keys, keyStr)
				valueStr, err := PropertyToString(value.Interface(), quotes)
				if err != nil {
					return "", err
				}
				elements[keyStr] = valueStr
			}
			sort.Strings(keys)
			pairs := make([]string, len(keys))
			for index, key := range keys {
				pairs[index] = key + ": " + elements[key]
			}
			return "[" + strings.Join(pairs, ", ") + "]", nil
		default:
			return "", fmt.Errorf("no serializer for type '%T'", object)
		}
	}
}
<|endoftext|>


<commit_before>// +build appengine

package gaebridge

// Only creates and returns a single Google App Engine (GAE) context, once per request.

import (
	"google.golang.org/appengine"
	"golang.org/x/net/context"
	"net/http"
)

var (
	ctx *context.Context

)

func Context(r *http.Request) *context.Context {
	if (ctx==nil) {
		newContext := appengine.NewContext(r);
		ctx = &newContext;

	}

	return ctx;

}<commit_msg>Grouting and general concurrency protection<commit_after>// +build appengine

package gaebridge

// Only creates and returns a single Google App Engine (GAE) context, once per request.

import (
	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"net/http"
	"sync"
)

var (
	mutex    sync.RWMutex
	contexts = make(map[*http.Request]*context.Context)
)

func Context(r *http.Request) *context.Context {

	mutex.RLock()
	if contexts[r] == nil { //Currently doesn't exist so make a new context
		mutex.RUnlock()
		mutex.Lock()
		newContext := appengine.NewContext(r)
		contexts[r] = &newContext
		mutex.Unlock()
		return &newContext
	} else {
		value := contexts[r]
		mutex.RUnlock()
		return value
	}
}

// Must be called directly prior to end of request cycle.
// This is to prevent a memory leak.
func CleanUp(r *http.Request) {
	mutex.Lock()
	delete(contexts, r)
	mutex.Unlock()
}
<|endoftext|>


<commit_before>// Copyright (c) 2015-2017 The libusb developers. All rights reserved.
// Project site: https://github.com/gotmc/libusb
// Use of this source code is governed by a MIT-style license that
// can be found in the LICENSE.txt file for the project.

package libusb

// #cgo pkg-config: libusb-1.0
// #include <libusb.h>
import "C"
import (
	"fmt"
	"reflect"
	"unsafe"
)

type LogLevel int

// Log message levels
//
// http://bit.ly/enum_libusb_log_level
const (
	LogLevelNone    LogLevel = C.LIBUSB_LOG_LEVEL_NONE
	LogLevelError   LogLevel = C.LIBUSB_LOG_LEVEL_ERROR
	LogLevelWarning LogLevel = C.LIBUSB_LOG_LEVEL_WARNING
	LogLevelInfo    LogLevel = C.LIBUSB_LOG_LEVEL_INFO
	LogLevelDebug   LogLevel = C.LIBUSB_LOG_LEVEL_DEBUG
)

var logLevels = map[LogLevel]string{
	LogLevelNone:    "No messages ever printed by the library (default)",
	LogLevelError:   "Error messages are printed to stderr",
	LogLevelWarning: "Warning and error messages are printed to stderr",
	LogLevelInfo:    "Informational messages are printed to stdout, warning and error messages are printed to stderr",
	LogLevelDebug:   "Debug and informational messages are printed to stdout, warnings and errors to stderr",
}

func (level LogLevel) String() string {
	return logLevels[level]
}

// Context represents a libusb session/context.
type Context struct {
	libusbContext *C.libusb_context
	LogLevel      LogLevel
}

// NewContext intializes a new libusb session/context by creating a new
// Context and returning a pointer to that Context.
func NewContext() (*Context, error) {
	newContext := &Context{
		LogLevel: LogLevelNone,
	}
	errnum := C.libusb_init(&newContext.libusbContext)
	if errnum != 0 {
		return nil, fmt.Errorf(
			"Failed to initialize new libusb context. Received error %d", errnum)
	}
	return newContext, nil
}

// Close deinitializes the libusb session/context.
func (ctx *Context) Close() error {
	C.libusb_exit(ctx.libusbContext)
	ctx.libusbContext = nil
	return nil
}

// SetDebug sets the log message verbosity.
func (ctx *Context) SetDebug(level LogLevel) {
	C.libusb_set_debug(ctx.libusbContext, C.int(level))
	ctx.LogLevel = level
	return
}

// GetDeviceList returns an array of devices for the context.
func (ctx *Context) GetDeviceList() ([]*Device, error) {
	var devices []*Device
	var list **C.libusb_device
	const unrefDevices = 1
	numDevicesFound := int(C.libusb_get_device_list(ctx.libusbContext, &list))
	if numDevicesFound < 0 {
		return nil, ErrorCode(numDevicesFound)
	}
	defer C.libusb_free_device_list(list, unrefDevices)
	var libusbDevices []*C.libusb_device
	*(*reflect.SliceHeader)(unsafe.Pointer(&libusbDevices)) = reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(list)),
		Len:  numDevicesFound,
		Cap:  numDevicesFound,
	}
	for _, thisLibusbDevice := range libusbDevices {
		thisDevice := Device{
			libusbDevice: thisLibusbDevice,
		}
		devices = append(devices, &thisDevice)
	}
	return devices, nil
}

// OpenDeviceWithVendorProduct opens a USB device using the VendorID and
// productID and then returns a device handle.
func (ctx *Context) OpenDeviceWithVendorProduct(
	vendorID uint16,
	productID uint16,
) (*Device, *DeviceHandle, error) {
	var deviceHandle DeviceHandle
	deviceHandle.libusbDeviceHandle = C.libusb_open_device_with_vid_pid(
		ctx.libusbContext, C.uint16_t(vendorID), C.uint16_t(productID))
	if deviceHandle.libusbDeviceHandle == nil {
		return nil, nil, fmt.Errorf("Could not open USB device %v:%v",
			vendorID,
			productID,
		)
	}
	device := Device{
		libusbDevice: C.libusb_get_device(deviceHandle.libusbDeviceHandle),
	}
	return &device, &deviceHandle, nil
}
<commit_msg>Fix breaking change in v1.0.22 libusb_set_debug<commit_after>// Copyright (c) 2015-2017 The libusb developers. All rights reserved.
// Project site: https://github.com/gotmc/libusb
// Use of this source code is governed by a MIT-style license that
// can be found in the LICENSE.txt file for the project.

package libusb

// #cgo pkg-config: libusb-1.0
// #include <libusb.h>
// int set_debug(libusb_context * ctx, int level) {
//   return libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level);
// }
import "C"
import (
	"fmt"
	"reflect"
	"unsafe"
)

type LogLevel int

// Log message levels
//
// http://bit.ly/enum_libusb_log_level
const (
	LogLevelNone    LogLevel = C.LIBUSB_LOG_LEVEL_NONE
	LogLevelError   LogLevel = C.LIBUSB_LOG_LEVEL_ERROR
	LogLevelWarning LogLevel = C.LIBUSB_LOG_LEVEL_WARNING
	LogLevelInfo    LogLevel = C.LIBUSB_LOG_LEVEL_INFO
	LogLevelDebug   LogLevel = C.LIBUSB_LOG_LEVEL_DEBUG
)

var logLevels = map[LogLevel]string{
	LogLevelNone:    "No messages ever printed by the library (default)",
	LogLevelError:   "Error messages are printed to stderr",
	LogLevelWarning: "Warning and error messages are printed to stderr",
	LogLevelInfo:    "Informational messages are printed to stdout, warning and error messages are printed to stderr",
	LogLevelDebug:   "Debug and informational messages are printed to stdout, warnings and errors to stderr",
}

func (level LogLevel) String() string {
	return logLevels[level]
}

// Context represents a libusb session/context.
type Context struct {
	libusbContext *C.libusb_context
	LogLevel      LogLevel
}

// NewContext intializes a new libusb session/context by creating a new
// Context and returning a pointer to that Context.
func NewContext() (*Context, error) {
	newContext := &Context{
		LogLevel: LogLevelNone,
	}
	errnum := C.libusb_init(&newContext.libusbContext)
	if errnum != 0 {
		return nil, fmt.Errorf(
			"Failed to initialize new libusb context. Received error %d", errnum)
	}
	return newContext, nil
}

// Close deinitializes the libusb session/context.
func (ctx *Context) Close() error {
	C.libusb_exit(ctx.libusbContext)
	ctx.libusbContext = nil
	return nil
}

// SetDebug sets the log message verbosity.
func (ctx *Context) SetDebug(level LogLevel) {
	C.set_debug(ctx.libusbContext, C.int(level))
	ctx.LogLevel = level
	return
}

// GetDeviceList returns an array of devices for the context.
func (ctx *Context) GetDeviceList() ([]*Device, error) {
	var devices []*Device
	var list **C.libusb_device
	const unrefDevices = 1
	numDevicesFound := int(C.libusb_get_device_list(ctx.libusbContext, &list))
	if numDevicesFound < 0 {
		return nil, ErrorCode(numDevicesFound)
	}
	defer C.libusb_free_device_list(list, unrefDevices)
	var libusbDevices []*C.libusb_device
	*(*reflect.SliceHeader)(unsafe.Pointer(&libusbDevices)) = reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(list)),
		Len:  numDevicesFound,
		Cap:  numDevicesFound,
	}
	for _, thisLibusbDevice := range libusbDevices {
		thisDevice := Device{
			libusbDevice: thisLibusbDevice,
		}
		devices = append(devices, &thisDevice)
	}
	return devices, nil
}

// OpenDeviceWithVendorProduct opens a USB device using the VendorID and
// productID and then returns a device handle.
func (ctx *Context) OpenDeviceWithVendorProduct(
	vendorID uint16,
	productID uint16,
) (*Device, *DeviceHandle, error) {
	var deviceHandle DeviceHandle
	deviceHandle.libusbDeviceHandle = C.libusb_open_device_with_vid_pid(
		ctx.libusbContext, C.uint16_t(vendorID), C.uint16_t(productID))
	if deviceHandle.libusbDeviceHandle == nil {
		return nil, nil, fmt.Errorf("Could not open USB device %v:%v",
			vendorID,
			productID,
		)
	}
	device := Device{
		libusbDevice: C.libusb_get_device(deviceHandle.libusbDeviceHandle),
	}
	return &device, &deviceHandle, nil
}
<|endoftext|>


<commit_before>package napnap

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net"
	"net/http"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
)

// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
	Key   string
	Value string
}

type Context struct {
	NapNap  *NapNap
	Request *http.Request
	Writer  ResponseWriter
	query   url.Values
	params  []Param
	store   map[string]interface{}
}

// NewContext returns a new context instance
func NewContext(napnap *NapNap, req *http.Request, writer ResponseWriter) *Context {
	return &Context{
		NapNap:  napnap,
		Request: req,
		Writer:  writer,
	}
}

// Render returns html format
func (c *Context) Render(code int, viewName string, data interface{}) error {
	c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
	c.Writer.WriteHeader(code)

	t, err := c.NapNap.template.Clone()
	if err != nil {
		return err
	}

	viewPath := path.Join(c.NapNap.templateRootPath, "views", viewName)
	t, err = t.ParseFiles(viewPath)
	if err != nil {
		return err
	}

	t.ExecuteTemplate(c.Writer, viewName, data)
	return nil
}

// String returns string format
func (c *Context) String(code int, s string) (err error) {
	c.Writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
	c.Writer.WriteHeader(code)
	c.Writer.Write([]byte(s))
	return
}

// JSON returns json format
func (c *Context) JSON(code int, i interface{}) (err error) {
	b, err := json.Marshal(i)
	if err != nil {
		return err
	}
	c.Writer.Header().Set("Content-Type", "application/json")
	c.Writer.WriteHeader(code)
	c.Writer.Write(b)
	return
}

// Redirect returns a HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) error {
	if (code < 300 || code > 308) && code != 201 {
		return fmt.Errorf("Cannot redirect with status code %d", code)
	}
	http.Redirect(c.Writer, c.Request, location, code)
	return nil
}

// BindJSON binds the request body into provided type `obj`. The default binder does
// it based on Content-Type header.
func (c *Context) BindJSON(obj interface{}) error {
	req := c.Request
	decoder := json.NewDecoder(req.Body)
	err := decoder.Decode(obj)
	if err != nil {
		return err
	}
	return nil
}

// Query returns query parameter by key.
func (c *Context) Query(key string) string {
	if c.query == nil {
		c.query = c.Request.URL.Query()
	}
	return c.query.Get(key)
}

// QueryInt returns query parameter by key and cast the value to int.
func (c *Context) QueryInt(key string) (int, error) {
	return strconv.Atoi(c.Query(key))
}

// QueryIntWithDefault returns query parameter by key and cast the value to int.  If the value doesn't exist, the default value will be used.
func (c *Context) QueryIntWithDefault(key string, defaultValue int) (int, error) {
	data := c.Query(key)
	if len(data) > 0 {
		return strconv.Atoi(c.Query(key))
	}
	return defaultValue, nil
}

// Form returns form parameter by key.
func (c *Context) Form(key string) string {
	req := c.Request
	if s := req.PostFormValue(key); len(s) > 0 {
		return s
	}
	if req.MultipartForm != nil {
		if values := req.MultipartForm.Value[key]; len(values) > 0 {
			return values[0]
		}
	}
	return ""
}

// FormFile returns file.
func (c *Context) FormFile(key string) (*multipart.FileHeader, error) {
	_, fh, err := c.Request.FormFile(key)
	return fh, err
}

// SaveUploadedFile uploads the form file to specific dst.
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	io.Copy(out, src)
	return nil
}

// Get retrieves data from the context.
func (c *Context) Get(key string) (interface{}, bool) {
	var value interface{}
	var exists bool
	if c.store != nil {
		value, exists = c.store[key]
	}
	return value, exists
}

// MustGet returns the value for the given key if it exists, otherwise it panics.
func (c *Context) MustGet(key string) interface{} {
	if value, exists := c.Get(key); exists {
		return value
	}
	panic("Key \"" + key + "\" does not exist")
}

// Set saves data in the context.
// It also lazy initializes  c.Keys if it was not used previously.
func (c *Context) Set(key string, val interface{}) {
	if c.store == nil {
		c.store = make(map[string]interface{})
	}
	c.store[key] = val
}

// Param returns form values by parameter
func (c *Context) Param(name string) string {
	for _, param := range c.params {
		if param.Key == name {
			return param.Value
		}
	}
	return ""
}

// ParamInt returns parameter by key and cast the value to int.
func (c *Context) ParamInt(key string) (int, error) {
	return strconv.Atoi(c.Param(key))
}

// ClientIP returns the remote ip address, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (c *Context) ClientIP() string {
	if c.NapNap.ForwardedByClientIP {
		clientIP := c.RequestHeader("X-Forwarded-For")
		if index := strings.IndexByte(clientIP, ','); index >= 0 {
			clientIP = clientIP[0:index]
		}
		clientIP = strings.TrimSpace(clientIP)
		if len(clientIP) > 0 {
			return clientIP
		}
		clientIP = strings.TrimSpace(c.RequestHeader("X-Real-Ip"))
		if len(clientIP) > 0 {
			return clientIP
		}
		clientIP = strings.TrimSpace(clientIP)
		if len(clientIP) > 0 {
			return clientIP
		}
	}
	if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
		return ip
	}
	return ""
}

// ContentType returns the Content-Type header of the request.
func (c *Context) ContentType() string {
	return filterFlags(c.Request.Header.Get("Content-Type"))
}

// SetCookie allows us to create an cookie
func (c *Context) SetCookie(
	name string,
	value string,
	maxAge int,
	path string,
	domain string,
	secure bool,
	httpOnly bool,
) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

// Cookie returns cookie value
func (c *Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	val, _ := url.QueryUnescape(cookie.Value)
	return val, nil
}

// SetStatus is a intelligent shortcut for c.Writer.WriteHeader(code)
func (c *Context) SetStatus(code int) {
	c.Writer.WriteHeader(code)
}

// Status is a intelligent shortcut for c.Writer.Status()
func (c *Context) Status() int {
	return c.Writer.Status()
}

// RespHeader is a intelligent shortcut for c.Writer.Header().Set(key, value)
// It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) RespHeader(key, value string) {
	if len(value) == 0 {
		c.Writer.Header().Del(key)
	} else {
		c.Writer.Header().Set(key, value)
	}
}

// RequestHeader is a intelligent shortcut for c.Request.Header.Get(key)
func (c *Context) RequestHeader(key string) string {
	return c.Request.Header.Get(key)
}

// StdContext return golang standard context
func (c *Context) StdContext() context.Context {
	ctx := c.Request.Context()
	ctx = newGContext(ctx, c)
	return ctx
}

// SetStdContext allow us to save the golang context to request
func (c *Context) SetStdContext(ctx context.Context) {
	c.Request = c.Request.WithContext(ctx)
}

// DeviceType returns user's device type which includes web, mobile, tab, tv
func (c *Context) DeviceType() string {
	userAgent := c.RequestHeader("User-Agent")
	deviceType := "web"

	if strings.Contains(userAgent, "Android") ||
		strings.Contains(userAgent, "webOS") ||
		strings.Contains(userAgent, "iPhone") ||
		strings.Contains(userAgent, "BlackBerry") ||
		strings.Contains(userAgent, "Windows Phone") {
		deviceType = "mobile"
	} else if strings.Contains(userAgent, "iPad") ||
		strings.Contains(userAgent, "iPod") ||
		(strings.Contains(userAgent, "tablet") ||
			strings.Contains(userAgent, "RX-34") ||
			strings.Contains(userAgent, "FOLIO")) ||
		(strings.Contains(userAgent, "Kindle") ||
			strings.Contains(userAgent, "Mac OS") &&
				strings.Contains(userAgent, "Silk")) ||
		(strings.Contains(userAgent, "AppleWebKit") &&
			strings.Contains(userAgent, "Silk")) {
		deviceType = "tab"
	} else if strings.Contains(userAgent, "TV") ||
		strings.Contains(userAgent, "NetCast") ||
		strings.Contains(userAgent, "boxee") ||
		strings.Contains(userAgent, "Kylo") ||
		strings.Contains(userAgent, "Roku") ||
		strings.Contains(userAgent, "DLNADOC") {
		deviceType = "tv"
	}
	return deviceType
}

func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
	c.Request = req
	c.Writer = c.Writer.reset(w)
	c.store = nil
	c.query = nil
	c.params = nil
}
<commit_msg>add comment for client ip function<commit_after>package napnap

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net"
	"net/http"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
)

// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
	Key   string
	Value string
}

type Context struct {
	NapNap  *NapNap
	Request *http.Request
	Writer  ResponseWriter
	query   url.Values
	params  []Param
	store   map[string]interface{}
}

// NewContext returns a new context instance
func NewContext(napnap *NapNap, req *http.Request, writer ResponseWriter) *Context {
	return &Context{
		NapNap:  napnap,
		Request: req,
		Writer:  writer,
	}
}

// Render returns html format
func (c *Context) Render(code int, viewName string, data interface{}) error {
	c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
	c.Writer.WriteHeader(code)

	t, err := c.NapNap.template.Clone()
	if err != nil {
		return err
	}

	viewPath := path.Join(c.NapNap.templateRootPath, "views", viewName)
	t, err = t.ParseFiles(viewPath)
	if err != nil {
		return err
	}

	t.ExecuteTemplate(c.Writer, viewName, data)
	return nil
}

// String returns string format
func (c *Context) String(code int, s string) (err error) {
	c.Writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
	c.Writer.WriteHeader(code)
	c.Writer.Write([]byte(s))
	return
}

// JSON returns json format
func (c *Context) JSON(code int, i interface{}) (err error) {
	b, err := json.Marshal(i)
	if err != nil {
		return err
	}
	c.Writer.Header().Set("Content-Type", "application/json")
	c.Writer.WriteHeader(code)
	c.Writer.Write(b)
	return
}

// Redirect returns a HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) error {
	if (code < 300 || code > 308) && code != 201 {
		return fmt.Errorf("Cannot redirect with status code %d", code)
	}
	http.Redirect(c.Writer, c.Request, location, code)
	return nil
}

// BindJSON binds the request body into provided type `obj`. The default binder does
// it based on Content-Type header.
func (c *Context) BindJSON(obj interface{}) error {
	req := c.Request
	decoder := json.NewDecoder(req.Body)
	err := decoder.Decode(obj)
	if err != nil {
		return err
	}
	return nil
}

// Query returns query parameter by key.
func (c *Context) Query(key string) string {
	if c.query == nil {
		c.query = c.Request.URL.Query()
	}
	return c.query.Get(key)
}

// QueryInt returns query parameter by key and cast the value to int.
func (c *Context) QueryInt(key string) (int, error) {
	return strconv.Atoi(c.Query(key))
}

// QueryIntWithDefault returns query parameter by key and cast the value to int.  If the value doesn't exist, the default value will be used.
func (c *Context) QueryIntWithDefault(key string, defaultValue int) (int, error) {
	data := c.Query(key)
	if len(data) > 0 {
		return strconv.Atoi(c.Query(key))
	}
	return defaultValue, nil
}

// Form returns form parameter by key.
func (c *Context) Form(key string) string {
	req := c.Request
	if s := req.PostFormValue(key); len(s) > 0 {
		return s
	}
	if req.MultipartForm != nil {
		if values := req.MultipartForm.Value[key]; len(values) > 0 {
			return values[0]
		}
	}
	return ""
}

// FormFile returns file.
func (c *Context) FormFile(key string) (*multipart.FileHeader, error) {
	_, fh, err := c.Request.FormFile(key)
	return fh, err
}

// SaveUploadedFile uploads the form file to specific dst.
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	io.Copy(out, src)
	return nil
}

// Get retrieves data from the context.
func (c *Context) Get(key string) (interface{}, bool) {
	var value interface{}
	var exists bool
	if c.store != nil {
		value, exists = c.store[key]
	}
	return value, exists
}

// MustGet returns the value for the given key if it exists, otherwise it panics.
func (c *Context) MustGet(key string) interface{} {
	if value, exists := c.Get(key); exists {
		return value
	}
	panic("Key \"" + key + "\" does not exist")
}

// Set saves data in the context.
// It also lazy initializes  c.Keys if it was not used previously.
func (c *Context) Set(key string, val interface{}) {
	if c.store == nil {
		c.store = make(map[string]interface{})
	}
	c.store[key] = val
}

// Param returns form values by parameter
func (c *Context) Param(name string) string {
	for _, param := range c.params {
		if param.Key == name {
			return param.Value
		}
	}
	return ""
}

// ParamInt returns parameter by key and cast the value to int.
func (c *Context) ParamInt(key string) (int, error) {
	return strconv.Atoi(c.Param(key))
}

// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
func (c *Context) ClientIP() string {
	if c.NapNap.ForwardedByClientIP {
		clientIP := c.RequestHeader("X-Forwarded-For")
		if index := strings.IndexByte(clientIP, ','); index >= 0 {
			clientIP = clientIP[0:index]
		}
		clientIP = strings.TrimSpace(clientIP)
		if len(clientIP) > 0 {
			return clientIP
		}
		clientIP = strings.TrimSpace(c.RequestHeader("X-Real-Ip"))
		if len(clientIP) > 0 {
			return clientIP
		}
		clientIP = strings.TrimSpace(clientIP)
		if len(clientIP) > 0 {
			return clientIP
		}
	}
	if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
		return ip
	}
	return ""
}

// ContentType returns the Content-Type header of the request.
func (c *Context) ContentType() string {
	return filterFlags(c.Request.Header.Get("Content-Type"))
}

// SetCookie allows us to create an cookie
func (c *Context) SetCookie(
	name string,
	value string,
	maxAge int,
	path string,
	domain string,
	secure bool,
	httpOnly bool,
) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

// Cookie returns cookie value
func (c *Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	val, _ := url.QueryUnescape(cookie.Value)
	return val, nil
}

// SetStatus is a intelligent shortcut for c.Writer.WriteHeader(code)
func (c *Context) SetStatus(code int) {
	c.Writer.WriteHeader(code)
}

// Status is a intelligent shortcut for c.Writer.Status()
func (c *Context) Status() int {
	return c.Writer.Status()
}

// RespHeader is a intelligent shortcut for c.Writer.Header().Set(key, value)
// It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) RespHeader(key, value string) {
	if len(value) == 0 {
		c.Writer.Header().Del(key)
	} else {
		c.Writer.Header().Set(key, value)
	}
}

// RequestHeader is a intelligent shortcut for c.Request.Header.Get(key)
func (c *Context) RequestHeader(key string) string {
	return c.Request.Header.Get(key)
}

// StdContext return golang standard context
func (c *Context) StdContext() context.Context {
	ctx := c.Request.Context()
	ctx = newGContext(ctx, c)
	return ctx
}

// SetStdContext allow us to save the golang context to request
func (c *Context) SetStdContext(ctx context.Context) {
	c.Request = c.Request.WithContext(ctx)
}

// DeviceType returns user's device type which includes web, mobile, tab, tv
func (c *Context) DeviceType() string {
	userAgent := c.RequestHeader("User-Agent")
	deviceType := "web"

	if strings.Contains(userAgent, "Android") ||
		strings.Contains(userAgent, "webOS") ||
		strings.Contains(userAgent, "iPhone") ||
		strings.Contains(userAgent, "BlackBerry") ||
		strings.Contains(userAgent, "Windows Phone") {
		deviceType = "mobile"
	} else if strings.Contains(userAgent, "iPad") ||
		strings.Contains(userAgent, "iPod") ||
		(strings.Contains(userAgent, "tablet") ||
			strings.Contains(userAgent, "RX-34") ||
			strings.Contains(userAgent, "FOLIO")) ||
		(strings.Contains(userAgent, "Kindle") ||
			strings.Contains(userAgent, "Mac OS") &&
				strings.Contains(userAgent, "Silk")) ||
		(strings.Contains(userAgent, "AppleWebKit") &&
			strings.Contains(userAgent, "Silk")) {
		deviceType = "tab"
	} else if strings.Contains(userAgent, "TV") ||
		strings.Contains(userAgent, "NetCast") ||
		strings.Contains(userAgent, "boxee") ||
		strings.Contains(userAgent, "Kylo") ||
		strings.Contains(userAgent, "Roku") ||
		strings.Contains(userAgent, "DLNADOC") {
		deviceType = "tv"
	}
	return deviceType
}

func (c *Context) reset(w http.ResponseWriter, req *http.Request) {
	c.Request = req
	c.Writer = c.Writer.reset(w)
	c.store = nil
	c.query = nil
	c.params = nil
}
<|endoftext|>


<commit_before>package chuper

import (
	"net/url"

	"github.com/PuerkitoBio/fetchbot"
)

type Context struct {
	*fetchbot.Context
	C Cache
}

func (c *Context) SourceURL() *url.URL {
	switch cmd := c.Cmd.(type) {
	case Cmd:
		return cmd.SourceURL()
	}
	return nil
}
<commit_msg>Rename Context field C to Cache<commit_after>package chuper

import (
	"net/url"

	"github.com/PuerkitoBio/fetchbot"
)

type Context struct {
	*fetchbot.Context
	Cache Cache
}

func (c *Context) SourceURL() *url.URL {
	switch cmd := c.Cmd.(type) {
	case Cmd:
		return cmd.SourceURL()
	default:
		return nil
	}
}
<|endoftext|>


<commit_before>package web

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
)

// createContext return a web.Context
func createContext(w http.ResponseWriter, r *http.Request, params *Params) *Context {

	ctx := &Context{
		ResponseWriter: w,
		Request:        r,
		params:         params,
	}

	return ctx
}

// Context is type of an web.Context
type Context struct {
	ResponseWriter http.ResponseWriter
	Request        *http.Request
	params         *Params
	urlValues      *url.Values
	UserID         uint64
}

// Param get value from Params
func (ctx *Context) Param(name string) string {
	return ctx.params.Val(name)
}

// Query get value from QueryString
func (ctx *Context) Query(name string) string {
	if ctx.urlValues == nil {
		urlValues := ctx.Request.URL.Query()
		ctx.urlValues = &urlValues
	}

	return ctx.urlValues.Get(name)
}

// Form get value from Form
func (ctx *Context) Form(name string) string {
	if ctx.Request.Form == nil {
		ctx.Request.ParseForm()
	}
	return ctx.Request.Form.Get(name)
}

// TryParse try parse val to v
func (ctx *Context) TryParse(val string, v interface{}) error {
	if v == nil {
		return errors.New("TryParse(nil)")
	}

	rv := reflect.ValueOf(v)

	if rv.Kind() != reflect.Ptr {
		return errors.New("TryParse(non-pointer " + reflect.TypeOf(v).String() + ")")
	}

	if rv.IsNil() {
		return errors.New("TryParse(nil)")
	}

	for rv.Kind() == reflect.Ptr && !rv.IsNil() {
		rv = rv.Elem()
	}

	if !rv.CanSet() {
		return errors.New("TryParse(can not set value to v)")
	}

	switch rv.Interface().(type) {
	case string:
		rv.SetString(val)
		return nil
	case int, int64:
		d, err := strconv.ParseInt(val, 10, 64)
		if err != nil {
			return err
		}
		rv.SetInt(d)
		return nil
	case int32:
		d, err := strconv.ParseInt(val, 10, 32)
		if err != nil {
			return err
		}
		rv.SetInt(d)
		return nil
	default:
		return json.Unmarshal([]byte(val), v)
	}
}

// Parse parse val to v, if error abort
func (ctx *Context) Parse(val string, v interface{}) {
	ctx.Abort(ctx.TryParse(val, v))
}

// TryParseBody decode val from Request.Body
func (ctx *Context) TryParseBody(val interface{}) error {
	if err := json.NewDecoder(ctx.Request.Body).Decode(val); err != nil {
		return err
	}
	defer ctx.Request.Body.Close()
	return nil
}

// ParseBody decode val from Request.Body, if error abort
func (ctx *Context) ParseBody(val interface{}) {
	ctx.Abort(ctx.TryParseBody(val))
}

// TryParseParam decode val from Query
func (ctx *Context) TryParseParam(name string, val interface{}) error {
	return ctx.TryParse(ctx.Param(name), val)
}

// ParseParam decode val from Param, if error abort
func (ctx *Context) ParseParam(name string, val interface{}) {
	ctx.Abort(ctx.TryParseParam(name, val))
}

// TryParseQuery decode val from Query
func (ctx *Context) TryParseQuery(name string, val interface{}) error {
	return ctx.TryParse(ctx.Query(name), val)
}

// ParseQuery decode val from Query, if error abort
func (ctx *Context) ParseQuery(name string, val interface{}) {
	ctx.Abort(ctx.TryParseQuery(name, val))
}

// TryParseForm decode val from Form
func (ctx *Context) TryParseForm(name string, val interface{}) error {
	return ctx.TryParse(ctx.Form(name), val)
}

// ParseForm decode val from Form, if error abort
func (ctx *Context) ParseForm(name string, val interface{}) {
	ctx.Abort(ctx.TryParseForm(name, val))
}

// Abort if error response err message with status 400 then abort
func (ctx *Context) Abort(err error) {
	if err != nil {
		ctx.WriteHeader(defaultHTTPError)
		ctx.WriteJSON(err.Error())
		panic(err)
	}
}

// AbortIf if error response err message with status 400 then abort
// else response val
func (ctx *Context) AbortIf(val interface{}, err error) {
	if err != nil {
		ctx.WriteHeader(defaultHTTPError)
		ctx.WriteJSON(err.Error())
		panic(err)
	} else {
		ctx.WriteHeader(defaultHTTPSuccess)
		ctx.WriteJSON(val)
	}
}

// Header get value by key from header
func (ctx *Context) Header(key string) string {
	return ctx.Request.Header.Get(key)
}

// Write bytes
func (ctx *Context) Write(val []byte) (int, error) {
	return ctx.ResponseWriter.Write(val)
}

// WriteString Write String
func (ctx *Context) WriteString(val string) (int, error) {
	return ctx.ResponseWriter.Write([]byte(val))
}

// WriteJSON Write JSON
func (ctx *Context) WriteJSON(val interface{}) error {
	return json.NewEncoder(ctx.ResponseWriter).Encode(val)
}

// WriteXML Write XML
func (ctx *Context) WriteXML(val interface{}) error {
	return xml.NewEncoder(ctx.ResponseWriter).Encode(val)
}

// WriteHeader Write Header
func (ctx *Context) WriteHeader(statusCode int) {
	ctx.ResponseWriter.WriteHeader(statusCode)
}

// SetHeader Set Header
func (ctx *Context) SetHeader(key string, value string) {
	ctx.ResponseWriter.Header().Set(key, value)
}

// AddHeader Add Header
func (ctx *Context) AddHeader(key string, value string) {
	ctx.ResponseWriter.Header().Add(key, value)
}

// SetContentType Set Content-Type
func (ctx *Context) SetContentType(val string) {
	ctx.SetHeader("Content-Type", contentType(val))
}

// Redirect to url with status
func (ctx *Context) Redirect(status int, url string) {
	ctx.SetHeader("Location", url)
	ctx.WriteHeader(status)
	ctx.WriteString("Redirecting to: " + url)
}
<commit_msg>added Unauthorized and Forbidden<commit_after>package web

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
)

// createContext return a web.Context
func createContext(w http.ResponseWriter, r *http.Request, params *Params) *Context {

	ctx := &Context{
		ResponseWriter: w,
		Request:        r,
		params:         params,
	}

	return ctx
}

// Context is type of an web.Context
type Context struct {
	ResponseWriter http.ResponseWriter
	Request        *http.Request
	params         *Params
	urlValues      *url.Values
	UserID         uint64
}

// Param get value from Params
func (ctx *Context) Param(name string) string {
	return ctx.params.Val(name)
}

// Query get value from QueryString
func (ctx *Context) Query(name string) string {
	if ctx.urlValues == nil {
		urlValues := ctx.Request.URL.Query()
		ctx.urlValues = &urlValues
	}

	return ctx.urlValues.Get(name)
}

// Form get value from Form
func (ctx *Context) Form(name string) string {
	if ctx.Request.Form == nil {
		ctx.Request.ParseForm()
	}
	return ctx.Request.Form.Get(name)
}

// TryParse try parse val to v
func (ctx *Context) TryParse(val string, v interface{}) error {
	if v == nil {
		return errors.New("TryParse(nil)")
	}

	rv := reflect.ValueOf(v)

	if rv.Kind() != reflect.Ptr {
		return errors.New("TryParse(non-pointer " + reflect.TypeOf(v).String() + ")")
	}

	if rv.IsNil() {
		return errors.New("TryParse(nil)")
	}

	for rv.Kind() == reflect.Ptr && !rv.IsNil() {
		rv = rv.Elem()
	}

	if !rv.CanSet() {
		return errors.New("TryParse(can not set value to v)")
	}

	switch rv.Interface().(type) {
	case string:
		rv.SetString(val)
		return nil
	case int, int64:
		d, err := strconv.ParseInt(val, 10, 64)
		if err != nil {
			return err
		}
		rv.SetInt(d)
		return nil
	case int32:
		d, err := strconv.ParseInt(val, 10, 32)
		if err != nil {
			return err
		}
		rv.SetInt(d)
		return nil
	default:
		return json.Unmarshal([]byte(val), v)
	}
}

// Parse parse val to v, if error abort
func (ctx *Context) Parse(val string, v interface{}) {
	ctx.Abort(ctx.TryParse(val, v))
}

// TryParseBody decode val from Request.Body
func (ctx *Context) TryParseBody(val interface{}) error {
	if err := json.NewDecoder(ctx.Request.Body).Decode(val); err != nil {
		return err
	}
	defer ctx.Request.Body.Close()
	return nil
}

// ParseBody decode val from Request.Body, if error abort
func (ctx *Context) ParseBody(val interface{}) {
	ctx.Abort(ctx.TryParseBody(val))
}

// TryParseParam decode val from Query
func (ctx *Context) TryParseParam(name string, val interface{}) error {
	return ctx.TryParse(ctx.Param(name), val)
}

// ParseParam decode val from Param, if error abort
func (ctx *Context) ParseParam(name string, val interface{}) {
	ctx.Abort(ctx.TryParseParam(name, val))
}

// TryParseQuery decode val from Query
func (ctx *Context) TryParseQuery(name string, val interface{}) error {
	return ctx.TryParse(ctx.Query(name), val)
}

// ParseQuery decode val from Query, if error abort
func (ctx *Context) ParseQuery(name string, val interface{}) {
	ctx.Abort(ctx.TryParseQuery(name, val))
}

// TryParseForm decode val from Form
func (ctx *Context) TryParseForm(name string, val interface{}) error {
	return ctx.TryParse(ctx.Form(name), val)
}

// ParseForm decode val from Form, if error abort
func (ctx *Context) ParseForm(name string, val interface{}) {
	ctx.Abort(ctx.TryParseForm(name, val))
}

// Abort if error about with 400
func (ctx *Context) Abort(err error) {
	if err != nil {
		ctx.WriteHeader(defaultHTTPError)
		ctx.WriteJSON(err.Error())
		panic(err)
	}
}

// AbortIf if error abort with 400
// else response val with 200
func (ctx *Context) AbortIf(val interface{}, err error) {
	if err != nil {
		ctx.WriteHeader(defaultHTTPError)
		ctx.WriteJSON(err.Error())
		panic(err)
	} else {
		ctx.WriteHeader(defaultHTTPSuccess)
		ctx.WriteJSON(val)
	}
}

// Unauthorized if error abort with 401
func (ctx *Context) Unauthorized(err error) {
	if err != nil {
		ctx.WriteHeader(401)
		ctx.WriteJSON(err.Error())
		panic(err)
	}
}

// Forbidden if error abort with 403
func (ctx *Context) Forbidden(err error) {
	if err != nil {
		ctx.WriteHeader(403)
		ctx.WriteJSON(err.Error())
		panic(err)
	}
}

// GetHeader get header by key
func (ctx *Context) GetHeader(key string) string {
	return ctx.Request.Header.Get(key)
}

// Write bytes
func (ctx *Context) Write(val []byte) (int, error) {
	return ctx.ResponseWriter.Write(val)
}

// WriteString Write String
func (ctx *Context) WriteString(val string) (int, error) {
	return ctx.ResponseWriter.Write([]byte(val))
}

// WriteJSON Write JSON
func (ctx *Context) WriteJSON(val interface{}) error {
	return json.NewEncoder(ctx.ResponseWriter).Encode(val)
}

// WriteXML Write XML
func (ctx *Context) WriteXML(val interface{}) error {
	return xml.NewEncoder(ctx.ResponseWriter).Encode(val)
}

// WriteHeader Write Header
func (ctx *Context) WriteHeader(statusCode int) {
	ctx.ResponseWriter.WriteHeader(statusCode)
}

// SetHeader Set Header
func (ctx *Context) SetHeader(key string, value string) {
	ctx.ResponseWriter.Header().Set(key, value)
}

// AddHeader Add Header
func (ctx *Context) AddHeader(key string, value string) {
	ctx.ResponseWriter.Header().Add(key, value)
}

// SetContentType Set Content-Type
func (ctx *Context) SetContentType(val string) {
	ctx.SetHeader("Content-Type", contentType(val))
}

// Redirect to url with status
func (ctx *Context) Redirect(status int, url string) {
	ctx.SetHeader("Location", url)
	ctx.WriteHeader(status)
	ctx.WriteString("Redirecting to: " + url)
}
<|endoftext|>


<commit_before>package fire

import (
	"net/http"

	"github.com/256dpi/fire/coal"

	"github.com/256dpi/jsonapi"
	"gopkg.in/mgo.v2/bson"
)

// An Operation indicates the purpose of a yield to a callback in the processing
// flow of an API request by a controller. These operations may occur multiple
// times during a single request.
type Operation int

// All the available operations.
const (
	_ Operation = iota

	// The list operation will used to authorize the loading of multiple
	// resources from a collection.
	//
	// Note: This operation is also used to load related resources.
	List

	// The find operation will be used to authorize the loading of a specific
	// resource from a collection.
	//
	// Note: This operation is also used to load a specific related resource.
	Find

	// The create operation will be used to authorize and validate the creation
	// of a new resource in a collection.
	Create

	// The update operation will be used to authorize the loading and validate
	// the updating of a specific resource in a collection.
	//
	// Note: Updates can include attributes, relationships or both.
	Update

	// The delete operation will be used to authorize the loading and validate
	// the deletion of a specific resource in a collection.
	Delete

	// The collection action operation will be used to authorize the execution
	// of a callback for a collection action.
	CollectionAction

	// The resource action operation will be used to authorize the execution
	// of a callback for a resource action.
	ResourceAction
)

// Read will return true when this operations does only read data.
func (o Operation) Read() bool {
	return o == List || o == Find
}

// Write will return true when this operation does write data.
func (o Operation) Write() bool {
	return o == Create || o == Update || o == Delete
}

// Action will return true when this operation is a collection or resource action.
func (o Operation) Action() bool {
	return o == CollectionAction || o == ResourceAction
}

// String returns the name of the operation.
func (o Operation) String() string {
	switch o {
	case List:
		return "List"
	case Find:
		return "Find"
	case Create:
		return "Create"
	case Update:
		return "Update"
	case Delete:
		return "Delete"
	case CollectionAction:
		return "CollectionAction"
	case ResourceAction:
		return "ResourceAction"
	}

	return ""
}

// A Context provides useful contextual information.
type Context struct {
	// The current operation in process.
	//
	// Usage: Read Only, Availability: Authorizers
	Operation Operation

	// The query that will be used during an List, Find, Update, Delete or
	// ResourceAction operation to select a list of models or a specific model.
	//
	// On Find, Update and Delete operations, the "_id" key is preset to the
	// resource id, while on forwarded List operations the relationship filter
	// is preset.
	//
	// Usage: Read Only, Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Selector bson.M

	// The filters that will be used during an List, Find, Update, Delete or
	// ResourceAction operation to further filter the selection of a list of
	// models or a specific model.
	//
	// On List operations, attribute and relationship filters are preset.
	//
	// Usage: Append Only, Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Filters []bson.M

	// The sorting that will be used during List.
	//
	// Usage: No Restriction, Availability: Authorizers
	// Operations: List
	Sorting []string

	// Only the whitelisted readable fields are exposed to the client as
	// attributes and relationships.
	//
	// Usage: Reduce Only, Availability: Authorizers
	// Operations: !Delete, !ResourceAction, !CollectionAction
	ReadableFields []string

	// Only the whitelisted writable fields can be altered by requests.
	//
	// Usage: Reduce Only, Availability: Authorizers
	// Operations: Create, Update
	WritableFields []string

	// The Model that will be created, updated or deleted.
	//
	// Usage: Modify Only, Availability: Validators
	// Operations: Create, Update, Delete
	Model coal.Model

	// The document that will be written to the client.
	//
	// Usage: Modify Only, Availability: Notifiers,
	// Operations: !CollectionAction, !ResourceAction
	Response *jsonapi.Document

	// The store that is used to retrieve and persist the model.
	//
	// Usage: Read Only
	Store *coal.SubStore

	// The underlying JSON-API request.
	//
	// Usage: Read Only
	JSONAPIRequest *jsonapi.Request

	// The underlying HTTP request.
	//
	// Note: The path is not updated when a controller forwards a request to
	// a related controller.
	//
	// Usage: Read Only
	HTTPRequest *http.Request

	// The underlying HTTP response writer. The response writer should only be
	// used during collection or resource actions to write a custom response.
	//
	// Usage: Read Only
	ResponseWriter http.ResponseWriter

	// The Controller that is managing the request.
	//
	// Usage: Read Only
	Controller *Controller

	// The Group that received the request.
	//
	// Usage: Read Only
	Group *Group

	// The Tracer used to tracer code execution.
	//
	// Usage: Read Only
	Tracer *Tracer

	original coal.Model
}

// Query returns the composite query of Selector and Filter.
func (c *Context) Query() bson.M {
	return bson.M{"$and": append([]bson.M{c.Selector}, c.Filters...)}
}

// Original will return the stored version of the model. This method is intended
// to be used to calculate the changed fields during an Update operation. Any
// returned error is already marked as fatal. This function will cache and reuse
// loaded models between multiple callbacks.
//
// Note: The method will panic if being used during any other operation than Update.
func (c *Context) Original() (coal.Model, error) {
	// begin trace
	c.Tracer.Push("fire/Context.Original")

	// check operation
	if c.Operation != Update {
		panic("fire: the original can only be loaded during an update operation")
	}

	// return cached model
	if c.original != nil {
		c.Tracer.Pop()
		return c.original, nil
	}

	// create a new model
	m := c.Model.Meta().Make()

	// read original document
	c.Tracer.Push("mgo/Query.One")
	c.Tracer.Tag("id", c.Model.ID())
	err := c.Store.C(c.Model).FindId(c.Model.ID()).One(m)
	if err != nil {
		return nil, err
	}
	c.Tracer.Pop()

	// cache model
	c.original = coal.Init(m)

	// finish trace
	c.Tracer.Pop()

	return c.original, nil
}
<commit_msg>fix docs<commit_after>package fire

import (
	"net/http"

	"github.com/256dpi/fire/coal"

	"github.com/256dpi/jsonapi"
	"gopkg.in/mgo.v2/bson"
)

// An Operation indicates the purpose of a yield to a callback in the processing
// flow of an API request by a controller. These operations may occur multiple
// times during a single request.
type Operation int

// All the available operations.
const (
	_ Operation = iota

	// The list operation will used to authorize the loading of multiple
	// resources from a collection.
	//
	// Note: This operation is also used to load related resources.
	List

	// The find operation will be used to authorize the loading of a specific
	// resource from a collection.
	//
	// Note: This operation is also used to load a specific related resource.
	Find

	// The create operation will be used to authorize and validate the creation
	// of a new resource in a collection.
	Create

	// The update operation will be used to authorize the loading and validate
	// the updating of a specific resource in a collection.
	//
	// Note: Updates can include attributes, relationships or both.
	Update

	// The delete operation will be used to authorize the loading and validate
	// the deletion of a specific resource in a collection.
	Delete

	// The collection action operation will be used to authorize the execution
	// of a callback for a collection action.
	CollectionAction

	// The resource action operation will be used to authorize the execution
	// of a callback for a resource action.
	ResourceAction
)

// Read will return true when this operations does only read data.
func (o Operation) Read() bool {
	return o == List || o == Find
}

// Write will return true when this operation does write data.
func (o Operation) Write() bool {
	return o == Create || o == Update || o == Delete
}

// Action will return true when this operation is a collection or resource action.
func (o Operation) Action() bool {
	return o == CollectionAction || o == ResourceAction
}

// String returns the name of the operation.
func (o Operation) String() string {
	switch o {
	case List:
		return "List"
	case Find:
		return "Find"
	case Create:
		return "Create"
	case Update:
		return "Update"
	case Delete:
		return "Delete"
	case CollectionAction:
		return "CollectionAction"
	case ResourceAction:
		return "ResourceAction"
	}

	return ""
}

// A Context provides useful contextual information.
type Context struct {
	// The current operation in process.
	//
	// Usage: Read Only, Availability: Authorizers
	Operation Operation

	// The query that will be used during an List, Find, Update, Delete or
	// ResourceAction operation to select a list of models or a specific model.
	//
	// On Find, Update and Delete operations, the "_id" key is preset to the
	// resource id, while on forwarded List operations the relationship filter
	// is preset.
	//
	// Usage: Read Only, Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Selector bson.M

	// The filters that will be used during an List, Find, Update, Delete or
	// ResourceAction operation to further filter the selection of a list of
	// models or a specific model.
	//
	// On List operations, attribute and relationship filters are preset.
	//
	// Usage: Append Only, Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Filters []bson.M

	// The sorting that will be used during List.
	//
	// Usage: No Restriction, Availability: Authorizers
	// Operations: List
	Sorting []string

	// Only the whitelisted readable fields are exposed to the client as
	// attributes and relationships.
	//
	// Usage: Reduce Only, Availability: Authorizers
	// Operations: !Delete, !ResourceAction, !CollectionAction
	ReadableFields []string

	// Only the whitelisted writable fields can be altered by requests.
	//
	// Usage: Reduce Only, Availability: Authorizers
	// Operations: Create, Update
	WritableFields []string

	// The Model that will be created, updated, deleted or is requested by a
	// resource action.
	//
	// Usage: Modify Only, Availability: Validators
	// Operations: Create, Update, Delete, ResourceAction
	Model coal.Model

	// The document that will be written to the client.
	//
	// Usage: Modify Only, Availability: Notifiers,
	// Operations: !CollectionAction, !ResourceAction
	Response *jsonapi.Document

	// The store that is used to retrieve and persist the model.
	//
	// Usage: Read Only
	Store *coal.SubStore

	// The underlying JSON-API request.
	//
	// Usage: Read Only
	JSONAPIRequest *jsonapi.Request

	// The underlying HTTP request.
	//
	// Note: The path is not updated when a controller forwards a request to
	// a related controller.
	//
	// Usage: Read Only
	HTTPRequest *http.Request

	// The underlying HTTP response writer. The response writer should only be
	// used during collection or resource actions to write a custom response.
	//
	// Usage: Read Only
	ResponseWriter http.ResponseWriter

	// The Controller that is managing the request.
	//
	// Usage: Read Only
	Controller *Controller

	// The Group that received the request.
	//
	// Usage: Read Only
	Group *Group

	// The Tracer used to tracer code execution.
	//
	// Usage: Read Only
	Tracer *Tracer

	original coal.Model
}

// Query returns the composite query of Selector and Filter.
func (c *Context) Query() bson.M {
	return bson.M{"$and": append([]bson.M{c.Selector}, c.Filters...)}
}

// Original will return the stored version of the model. This method is intended
// to be used to calculate the changed fields during an Update operation. Any
// returned error is already marked as fatal. This function will cache and reuse
// loaded models between multiple callbacks.
//
// Note: The method will panic if being used during any other operation than Update.
func (c *Context) Original() (coal.Model, error) {
	// begin trace
	c.Tracer.Push("fire/Context.Original")

	// check operation
	if c.Operation != Update {
		panic("fire: the original can only be loaded during an update operation")
	}

	// return cached model
	if c.original != nil {
		c.Tracer.Pop()
		return c.original, nil
	}

	// create a new model
	m := c.Model.Meta().Make()

	// read original document
	c.Tracer.Push("mgo/Query.One")
	c.Tracer.Tag("id", c.Model.ID())
	err := c.Store.C(c.Model).FindId(c.Model.ID()).One(m)
	if err != nil {
		return nil, err
	}
	c.Tracer.Pop()

	// cache model
	c.original = coal.Init(m)

	// finish trace
	c.Tracer.Pop()

	return c.original, nil
}
<|endoftext|>


<commit_before>package lars

import (
	"io"
	"net"
	"net/http"
	"reflect"
	"runtime"
	"strings"

	"golang.org/x/net/context"
)

// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
	Key   string
	Value string
}

// Params is a Param-slice, as returned by the router.
// The slice is ordered, the first URL parameter is also the first slice value.
// It is therefore safe to read values by the index.
type Params []Param

type store map[string]interface{}

// IAppContext is an interface for an AppContext http request object that can be passed
// around and allocated efficiently; and most importantly is not tied to the
// context object and can be passed around separately if desired instead of Context
// being the interface, which does not have a clear separation of http Context vs App Context
type IAppContext interface {
	Reset(*Context)
	Done()
}

// Context encapsulates the http request, response context
type Context struct {
	context.Context
	Request             *http.Request
	Response            *Response
	AppContext          IAppContext
	params              Params
	handlers            HandlersChain
	store               store
	index               int
	formParsed          bool
	multipartFormParsed bool
}

var _ context.Context = &Context{}

// newContext returns a new default lars Context object.
func newContext(l *LARS) *Context {

	c := &Context{
		params:     make(Params, l.mostParams),
		AppContext: l.newAppContext(),
	}

	c.Response = newResponse(nil, c)

	return c
}

// reset resets the Context to it's default request state
func (c *Context) reset(w http.ResponseWriter, r *http.Request) {
	c.Request = r
	c.Response.reset(w)
	c.params = c.params[0:0]
	c.store = nil
	c.index = -1
	c.handlers = nil
	c.formParsed = false
	c.multipartFormParsed = false
}

// Param returns the value of the first Param which key matches the given name.
// If no matching Param is found, an empty string is returned.
func (c *Context) Param(name string) string {

	for _, entry := range c.params {
		if entry.Key == name {
			return entry.Value
		}
	}

	return blank
}

// ParseForm calls the underlying http.Request ParseForm
// but also adds the URL params to the request Form as if
// they were defined as query params i.e. ?id=13&ok=true but
// does not add the params to the http.Request.URL.RawQuery
// for SEO purposes
func (c *Context) ParseForm() error {

	if c.formParsed {
		return nil
	}

	if err := c.Request.ParseForm(); err != nil {
		return err
	}

	for _, entry := range c.params {
		c.Request.Form[entry.Key] = []string{entry.Value}
	}

	c.formParsed = true

	return nil
}

// ParseMultipartForm calls the underlying http.Request ParseMultipartForm
// but also adds the URL params to the request Form as if they were defined
// as query params i.e. ?id=13&ok=true but does not add the params to the
// http.Request.URL.RawQuery for SEO purposes
func (c *Context) ParseMultipartForm(maxMemory int64) error {

	if c.multipartFormParsed {
		return nil
	}

	if err := c.Request.ParseMultipartForm(maxMemory); err != nil {
		return err
	}

	for _, entry := range c.params {
		c.Request.Form[entry.Key] = []string{entry.Value}
	}

	c.multipartFormParsed = true

	return nil
}

// Set is used to store a new key/value pair exclusivelly for this*Context.
// It also lazy initializes  c.Keys if it was not used previously.
func (c *Context) Set(key string, value interface{}) {
	if c.store == nil {
		c.store = make(store)
	}
	c.store[key] = value
}

// Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false)
func (c *Context) Get(key string) (value interface{}, exists bool) {
	if c.store != nil {
		value, exists = c.store[key]
	}
	return
}

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func (c *Context) Next() {
	c.index++
	c.handlers[c.index](c)
}

// http request helpers

// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (c *Context) ClientIP() (clientIP string) {

	var values []string

	if values, _ = c.Request.Header[XRealIP]; len(values) > 0 {

		clientIP = strings.TrimSpace(values[0])
		if clientIP != blank {
			return
		}
	}

	if values, _ = c.Request.Header[XForwardedFor]; len(values) > 0 {
		clientIP = values[0]

		if index := strings.IndexByte(clientIP, ','); index >= 0 {
			clientIP = clientIP[0:index]
		}

		clientIP = strings.TrimSpace(clientIP)
		if clientIP != blank {
			return
		}
	}

	clientIP, _, _ = net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))

	return
}

// AcceptedLanguages returns an array of accepted languages denoted by
// the Accept-Language header sent by the browser
// NOTE: this lowercases the locales as some stupid browsers send in lowercase
// when all the rest send it properly
func (c *Context) AcceptedLanguages(lowercase bool) []string {

	var accepted string

	if accepted = c.Request.Header.Get(AcceptedLanguage); accepted == blank {
		return []string{}
	}

	options := strings.Split(accepted, ",")
	l := len(options)

	language := make([]string, l)

	for i := 0; i < l; i++ {
		locale := strings.SplitN(options[i], ";", 2)

		if lowercase {
			language[i] = strings.ToLower(strings.Trim(locale[0], " "))
			continue
		}

		language[i] = strings.Trim(locale[0], " ")
	}

	return language
}

// HandlerName returns the current Contexts final handler name
// NOTE: this only works for lars HandlerFunc i.e. func(*Context)
// as native middleware functions are wrapped
func (c *Context) HandlerName() string {

	if c.handlers == nil || len(c.handlers) == 0 {
		return blank
	}

	handler := c.handlers[len(c.handlers)-1]

	return runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name()
}

// Stream provides HTTP Streaming
func (c *Context) Stream(step func(w io.Writer) bool) {
	w := c.Response
	clientGone := w.CloseNotify()

	for {
		select {
		case <-clientGone:
			return
		default:
			keepOpen := step(w)
			w.Flush()
			if !keepOpen {
				return
			}
		}
	}
}
<commit_msg>update AcceptLanguages NOTE<commit_after>package lars

import (
	"io"
	"net"
	"net/http"
	"reflect"
	"runtime"
	"strings"

	"golang.org/x/net/context"
)

// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
	Key   string
	Value string
}

// Params is a Param-slice, as returned by the router.
// The slice is ordered, the first URL parameter is also the first slice value.
// It is therefore safe to read values by the index.
type Params []Param

type store map[string]interface{}

// IAppContext is an interface for an AppContext http request object that can be passed
// around and allocated efficiently; and most importantly is not tied to the
// context object and can be passed around separately if desired instead of Context
// being the interface, which does not have a clear separation of http Context vs App Context
type IAppContext interface {
	Reset(*Context)
	Done()
}

// Context encapsulates the http request, response context
type Context struct {
	context.Context
	Request             *http.Request
	Response            *Response
	AppContext          IAppContext
	params              Params
	handlers            HandlersChain
	store               store
	index               int
	formParsed          bool
	multipartFormParsed bool
}

var _ context.Context = &Context{}

// newContext returns a new default lars Context object.
func newContext(l *LARS) *Context {

	c := &Context{
		params:     make(Params, l.mostParams),
		AppContext: l.newAppContext(),
	}

	c.Response = newResponse(nil, c)

	return c
}

// reset resets the Context to it's default request state
func (c *Context) reset(w http.ResponseWriter, r *http.Request) {
	c.Request = r
	c.Response.reset(w)
	c.params = c.params[0:0]
	c.store = nil
	c.index = -1
	c.handlers = nil
	c.formParsed = false
	c.multipartFormParsed = false
}

// Param returns the value of the first Param which key matches the given name.
// If no matching Param is found, an empty string is returned.
func (c *Context) Param(name string) string {

	for _, entry := range c.params {
		if entry.Key == name {
			return entry.Value
		}
	}

	return blank
}

// ParseForm calls the underlying http.Request ParseForm
// but also adds the URL params to the request Form as if
// they were defined as query params i.e. ?id=13&ok=true but
// does not add the params to the http.Request.URL.RawQuery
// for SEO purposes
func (c *Context) ParseForm() error {

	if c.formParsed {
		return nil
	}

	if err := c.Request.ParseForm(); err != nil {
		return err
	}

	for _, entry := range c.params {
		c.Request.Form[entry.Key] = []string{entry.Value}
	}

	c.formParsed = true

	return nil
}

// ParseMultipartForm calls the underlying http.Request ParseMultipartForm
// but also adds the URL params to the request Form as if they were defined
// as query params i.e. ?id=13&ok=true but does not add the params to the
// http.Request.URL.RawQuery for SEO purposes
func (c *Context) ParseMultipartForm(maxMemory int64) error {

	if c.multipartFormParsed {
		return nil
	}

	if err := c.Request.ParseMultipartForm(maxMemory); err != nil {
		return err
	}

	for _, entry := range c.params {
		c.Request.Form[entry.Key] = []string{entry.Value}
	}

	c.multipartFormParsed = true

	return nil
}

// Set is used to store a new key/value pair exclusivelly for this*Context.
// It also lazy initializes  c.Keys if it was not used previously.
func (c *Context) Set(key string, value interface{}) {
	if c.store == nil {
		c.store = make(store)
	}
	c.store[key] = value
}

// Get returns the value for the given key, ie: (value, true).
// If the value does not exists it returns (nil, false)
func (c *Context) Get(key string) (value interface{}, exists bool) {
	if c.store != nil {
		value, exists = c.store[key]
	}
	return
}

// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func (c *Context) Next() {
	c.index++
	c.handlers[c.index](c)
}

// http request helpers

// ClientIP implements a best effort algorithm to return the real client IP, it parses
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
func (c *Context) ClientIP() (clientIP string) {

	var values []string

	if values, _ = c.Request.Header[XRealIP]; len(values) > 0 {

		clientIP = strings.TrimSpace(values[0])
		if clientIP != blank {
			return
		}
	}

	if values, _ = c.Request.Header[XForwardedFor]; len(values) > 0 {
		clientIP = values[0]

		if index := strings.IndexByte(clientIP, ','); index >= 0 {
			clientIP = clientIP[0:index]
		}

		clientIP = strings.TrimSpace(clientIP)
		if clientIP != blank {
			return
		}
	}

	clientIP, _, _ = net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))

	return
}

// AcceptedLanguages returns an array of accepted languages denoted by
// the Accept-Language header sent by the browser
// NOTE: some stupid browsers send in locales lowercase when all the rest send it properly
func (c *Context) AcceptedLanguages(lowercase bool) []string {

	var accepted string

	if accepted = c.Request.Header.Get(AcceptedLanguage); accepted == blank {
		return []string{}
	}

	options := strings.Split(accepted, ",")
	l := len(options)

	language := make([]string, l)

	for i := 0; i < l; i++ {
		locale := strings.SplitN(options[i], ";", 2)

		if lowercase {
			language[i] = strings.ToLower(strings.Trim(locale[0], " "))
			continue
		}

		language[i] = strings.Trim(locale[0], " ")
	}

	return language
}

// HandlerName returns the current Contexts final handler name
// NOTE: this only works for lars HandlerFunc i.e. func(*Context)
// as native middleware functions are wrapped
func (c *Context) HandlerName() string {

	if c.handlers == nil || len(c.handlers) == 0 {
		return blank
	}

	handler := c.handlers[len(c.handlers)-1]

	return runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name()
}

// Stream provides HTTP Streaming
func (c *Context) Stream(step func(w io.Writer) bool) {
	w := c.Response
	clientGone := w.CloseNotify()

	for {
		select {
		case <-clientGone:
			return
		default:
			keepOpen := step(w)
			w.Flush()
			if !keepOpen {
				return
			}
		}
	}
}
<|endoftext|>


<commit_before>package context

import (
	"sync"
)

var (
	contexts = make(map[uint64]*context)
	global   = make(Map)

	allmx sync.RWMutex
)

// Contextual is an interface for anything that maintains its own context.
type Contextual interface {
	// Fill fills the given Map with all of this Contextual's context
	Fill(m Map)
}

// Map is a map of key->value pairs.
type Map map[string]interface{}

// Fill implements the method from the Contextual interface.
func (_m Map) Fill(m Map) {
	for key, value := range _m {
		m[key] = value
	}
}

// Context is a context containing key->value pairs
type Context interface {
	// Enter enters a new level on this Context stack.
	Enter() Context

	// Go starts the given function on a new goroutine.
	Go(fn func())

	// Exit exits the current level on this Context stack.
	Exit() Context

	// Put puts a key->value pair into the current level of the context stack.
	Put(key string, value interface{}) Context

	// PutDynamic puts a key->value pair into the current level of the context stack
	// where the value is generated by a function that gets evaluated at every Read.
	PutDynamic(key string, valueFN func() interface{}) Context
}

type context struct {
	id           uint64
	parent       *context
	branchedFrom *context
	data         Map
	mx           sync.RWMutex
}

type dynval struct {
	fn func() interface{}
}

// Enter enters a new level on the current Context stack, creating a new Context
// if necessary.
func Enter() Context {
	id := curGoroutineID()
	allmx.Lock()
	c := contexts[id]
	if c == nil {
		c = makeContext(id, nil, nil)
		contexts[id] = c
		allmx.Unlock()
		return c
	}
	allmx.Unlock()
	return c.Enter()
}

// Enter enters a new level on this Context stack.
func (c *context) Enter() Context {
	c.mx.RLock()
	id := c.id
	c.mx.RUnlock()
	next := makeContext(id, c, nil)
	allmx.Lock()
	contexts[id] = next
	allmx.Unlock()
	return next
}

// Go starts the given function on a new goroutine.
func (c *context) Go(fn func()) {
	go func() {
		id := curGoroutineID()
		next := makeContext(id, nil, c)
		allmx.Lock()
		contexts[id] = next
		allmx.Unlock()
		fn()
		// Clean up the context
		allmx.Lock()
		delete(contexts, id)
		allmx.Unlock()
	}()
}

// Go starts the given function on a new goroutine but sharing the context of
// the current goroutine (if it has one).
func Go(fn func()) {
	c := currentContext()
	if c != nil {
		c.Go(fn)
	} else {
		go fn()
	}
}

func makeContext(id uint64, parent *context, branchedFrom *context) *context {
	return &context{
		id:           id,
		parent:       parent,
		branchedFrom: branchedFrom,
		data:         make(Map),
	}
}

// Exit exits the current level on this Context stack.
func (c *context) Exit() Context {
	c.mx.RLock()
	id := c.id
	parent := c.parent
	c.mx.RUnlock()
	if parent == nil {
		allmx.Lock()
		delete(contexts, id)
		allmx.Unlock()
		return nil
	}
	allmx.Lock()
	contexts[id] = parent
	allmx.Unlock()
	return parent
}

// Put puts a key->value pair into the current level of the context stack.
func (c *context) Put(key string, value interface{}) Context {
	c.mx.Lock()
	c.data[key] = value
	c.mx.Unlock()
	return c
}

// PutGlobal puts the given key->value pair into the globalc context.
func PutGlobal(key string, value interface{}) {
	allmx.Lock()
	global[key] = value
	allmx.Unlock()
}

// PutDynamic puts a key->value pair into the current level of the context stack
// where the value is generated by a function that gets evaluated at every Read.
func (c *context) PutDynamic(key string, valueFN func() interface{}) Context {
	value := &dynval{valueFN}
	c.mx.Lock()
	c.data[key] = value
	c.mx.Unlock()
	return c
}

// PutGlobalDynamic puts a key->value pair into the global context wwhere the value is
// generated by a function that gets evaluated at every Read.
func PutGlobalDynamic(key string, valueFN func() interface{}) {
	value := &dynval{valueFN}
	allmx.Lock()
	global[key] = value
	allmx.Unlock()
}

func (c *context) fill(m Map) {
	for ctx := c; ctx != nil; {
		ctx.mx.RLock()
		fill(m, ctx.data)
		next := ctx.parent
		if next == nil {
			next = ctx.branchedFrom
		}
		ctx.mx.RUnlock()
		ctx = next
	}
}

// AsMap returns a map containing all values from the supplied obj if it is a
// Contextual, plus any addition values from along the stack, plus globals if so
// specified.
func AsMap(obj interface{}, includeGlobals bool) Map {
	result := make(Map, 0)
	cl, ok := obj.(Contextual)
	if ok {
		cl.Fill(result)
	}
	c := currentContext()
	if c != nil {
		c.fill(result)
	}
	if includeGlobals {
		allmx.RLock()
		fill(result, global)
		allmx.RUnlock()
	}
	return result
}

func fill(m Map, from Map) {
	if m != nil {
		for key, value := range from {
			_, alreadyRead := m[key]
			if !alreadyRead {
				switch v := value.(type) {
				case *dynval:
					m[key] = v.fn()
				default:
					m[key] = v
				}
			}
		}
	}
}

func currentContext() *context {
	id := curGoroutineID()
	allmx.RLock()
	c := contexts[id]
	allmx.RUnlock()
	return c
}
<commit_msg>Removed extraneous comments<commit_after>package context

import (
	"sync"
)

var (
	contexts = make(map[uint64]*context)
	global   = make(Map)

	allmx sync.RWMutex
)

// Contextual is an interface for anything that maintains its own context.
type Contextual interface {
	// Fill fills the given Map with all of this Contextual's context
	Fill(m Map)
}

// Map is a map of key->value pairs.
type Map map[string]interface{}

// Fill implements the method from the Contextual interface.
func (_m Map) Fill(m Map) {
	for key, value := range _m {
		m[key] = value
	}
}

// Context is a context containing key->value pairs
type Context interface {
	// Enter enters a new level on this Context stack.
	Enter() Context

	// Go starts the given function on a new goroutine.
	Go(fn func())

	// Exit exits the current level on this Context stack.
	Exit() Context

	// Put puts a key->value pair into the current level of the context stack.
	Put(key string, value interface{}) Context

	// PutDynamic puts a key->value pair into the current level of the context stack
	// where the value is generated by a function that gets evaluated at every Read.
	PutDynamic(key string, valueFN func() interface{}) Context
}

type context struct {
	id           uint64
	parent       *context
	branchedFrom *context
	data         Map
	mx           sync.RWMutex
}

type dynval struct {
	fn func() interface{}
}

// Enter enters a new level on the current Context stack, creating a new Context
// if necessary.
func Enter() Context {
	id := curGoroutineID()
	allmx.Lock()
	c := contexts[id]
	if c == nil {
		c = makeContext(id, nil, nil)
		contexts[id] = c
		allmx.Unlock()
		return c
	}
	allmx.Unlock()
	return c.Enter()
}

func (c *context) Enter() Context {
	c.mx.RLock()
	id := c.id
	c.mx.RUnlock()
	next := makeContext(id, c, nil)
	allmx.Lock()
	contexts[id] = next
	allmx.Unlock()
	return next
}

func (c *context) Go(fn func()) {
	go func() {
		id := curGoroutineID()
		next := makeContext(id, nil, c)
		allmx.Lock()
		contexts[id] = next
		allmx.Unlock()
		fn()
		// Clean up the context
		allmx.Lock()
		delete(contexts, id)
		allmx.Unlock()
	}()
}

// Go starts the given function on a new goroutine but sharing the context of
// the current goroutine (if it has one).
func Go(fn func()) {
	c := currentContext()
	if c != nil {
		c.Go(fn)
	} else {
		go fn()
	}
}

func makeContext(id uint64, parent *context, branchedFrom *context) *context {
	return &context{
		id:           id,
		parent:       parent,
		branchedFrom: branchedFrom,
		data:         make(Map),
	}
}

func (c *context) Exit() Context {
	c.mx.RLock()
	id := c.id
	parent := c.parent
	c.mx.RUnlock()
	if parent == nil {
		allmx.Lock()
		delete(contexts, id)
		allmx.Unlock()
		return nil
	}
	allmx.Lock()
	contexts[id] = parent
	allmx.Unlock()
	return parent
}

func (c *context) Put(key string, value interface{}) Context {
	c.mx.Lock()
	c.data[key] = value
	c.mx.Unlock()
	return c
}

// PutGlobal puts the given key->value pair into the globalc context.
func PutGlobal(key string, value interface{}) {
	allmx.Lock()
	global[key] = value
	allmx.Unlock()
}

func (c *context) PutDynamic(key string, valueFN func() interface{}) Context {
	value := &dynval{valueFN}
	c.mx.Lock()
	c.data[key] = value
	c.mx.Unlock()
	return c
}

// PutGlobalDynamic puts a key->value pair into the global context wwhere the value is
// generated by a function that gets evaluated at every Read.
func PutGlobalDynamic(key string, valueFN func() interface{}) {
	value := &dynval{valueFN}
	allmx.Lock()
	global[key] = value
	allmx.Unlock()
}

func (c *context) fill(m Map) {
	for ctx := c; ctx != nil; {
		ctx.mx.RLock()
		fill(m, ctx.data)
		next := ctx.parent
		if next == nil {
			next = ctx.branchedFrom
		}
		ctx.mx.RUnlock()
		ctx = next
	}
}

// AsMap returns a map containing all values from the supplied obj if it is a
// Contextual, plus any addition values from along the stack, plus globals if so
// specified.
func AsMap(obj interface{}, includeGlobals bool) Map {
	result := make(Map, 0)
	cl, ok := obj.(Contextual)
	if ok {
		cl.Fill(result)
	}
	c := currentContext()
	if c != nil {
		c.fill(result)
	}
	if includeGlobals {
		allmx.RLock()
		fill(result, global)
		allmx.RUnlock()
	}
	return result
}

func fill(m Map, from Map) {
	if m != nil {
		for key, value := range from {
			_, alreadyRead := m[key]
			if !alreadyRead {
				switch v := value.(type) {
				case *dynval:
					m[key] = v.fn()
				default:
					m[key] = v
				}
			}
		}
	}
}

func currentContext() *context {
	id := curGoroutineID()
	allmx.RLock()
	c := contexts[id]
	allmx.RUnlock()
	return c
}
<|endoftext|>


<commit_before>package main

import (
	"container/ring"
	"encoding/json"
	"errors"
	"github.com/powerman/rpc-codec/jsonrpc2"
	"golang.org/x/net/websocket"
	"net/rpc"
	"time"
)

type Context struct {
	streams map[string]*BroadcastContext
	timeout time.Duration
}

type BroadcastContext struct {
	Broadcast
	// There is a timeout after releasing a stream during which it is possible
	// to reacquire the same object and continue broadcasting. Once the timeout
	// elapses, the stream is closed for good.
	ping    chan int
	closing bool

	chatHistory *ring.Ring
	chatViewers map[*chatContext]interface{}
	chatRoster  map[string]*chatContext
}

type chatContext struct {
	name   string
	socket *websocket.Conn
	stream *BroadcastContext
}

type chatMessage struct {
	name string
	text string
}

func NewContext(timeout time.Duration) Context {
	return Context{
		timeout: timeout, streams: make(map[string]*BroadcastContext),
	}
}

// Acquire a stream for writing. Only one "writable" reference can be held;
// until it is released, this function will return an error.
func (ctx *Context) Acquire(id string) (*BroadcastContext, bool) {
	stream, ok := ctx.streams[id]

	if !ok {
		v := BroadcastContext{
			Broadcast:   NewBroadcast(),
			ping:        make(chan int),
			closing:     false,
			chatHistory: ring.New(20),
			chatViewers: make(map[*chatContext]interface{}),
			chatRoster:  make(map[string]*chatContext),
		}

		ctx.streams[id] = &v
		go ctx.closeOnRelease(id, &v)
		return &v, true
	}

	if !stream.closing {
		return nil, false
	}

	stream.closing = false
	stream.ping <- 1
	return stream, true
}

func (stream *BroadcastContext) Release() {
	stream.closing = true
	stream.ping <- 1
}

// Acquire a stream for reading. There is no limit on the number of concurrent readers.
func (ctx *Context) Get(id string) (*BroadcastContext, bool) {
	stream, ok := ctx.streams[id]
	return stream, ok
}

func (ctx *Context) closeOnRelease(id string, stream *BroadcastContext) {
	for {
		if stream.closing {
			timer := time.NewTimer(ctx.timeout)

			select {
			case <-stream.ping:
				timer.Stop()
			case <-timer.C:
				delete(ctx.streams, id)
				stream.Close()
				return
			}
		} else {
			<-stream.ping
		}
	}
}

type ChatSetNameArgs struct {
	Name string
}

func (x *ChatSetNameArgs) UnmarshalJSON(buf []byte) error {
	fields := []interface{}{&x.Name}
	expect := len(fields)
	if err := json.Unmarshal(buf, &fields); err != nil {
		return err
	}
	if len(fields) != expect {
		return errors.New("invalid number of arguments")
	}
	return nil
}

func (ctx *chatContext) SetName(args *ChatSetNameArgs, _ *interface{}) error {
	// TODO check that the name is alphanumeric
	// TODO check that the name is not too long
	if _, ok := ctx.stream.chatRoster[args.Name]; ok {
		return errors.New("name already taken")
	}

	ctx.stream.chatRoster[args.Name] = ctx
	if ctx.name != "" {
		delete(ctx.stream.chatRoster, ctx.name)
	}
	ctx.name = args.Name
	return nil
}

type ChatSendMessageArgs struct {
	Text string
}

func (x *ChatSendMessageArgs) UnmarshalJSON(buf []byte) error {
	fields := []interface{}{&x.Text}
	expect := len(fields)
	if err := json.Unmarshal(buf, &fields); err != nil {
		return err
	}
	if len(fields) != expect {
		return errors.New("invalid number of arguments")
	}
	return nil
}

func (ctx *chatContext) SendMessage(args *ChatSendMessageArgs, _ *interface{}) error {
	// TODO check that the message is not whitespace-only
	// TODO check that the message is not too long
	if ctx.name == "" {
		return errors.New("must obtain a name first")
	}

	msg := chatMessage{ctx.name, args.Text}

	for viewer := range ctx.stream.chatViewers {
		viewer.onMessage(msg)
	}

	ctx.stream.chatHistory.Value = msg
	ctx.stream.chatHistory = ctx.stream.chatHistory.Next()
	return nil
}

func (ctx *chatContext) RequestHistory(_ *interface{}, _ *interface{}) error {
	r := ctx.stream.chatHistory

	for i := 0; i < r.Len(); i++ {
		if r.Value != nil {
			ctx.onMessage(r.Value.(chatMessage))
		}
		r = r.Next()
	}

	return nil
}

func (stream *BroadcastContext) RunRPC(ws *websocket.Conn) {
	chatter := chatContext{name: "", socket: ws, stream: stream}

	stream.chatViewers[&chatter] = nil
	defer func() {
		delete(stream.chatViewers, &chatter)

		if chatter.name != "" {
			delete(stream.chatRoster, chatter.name)
		}
	}()

	server := rpc.NewServer()
	server.RegisterName("Chat", &chatter)
	server.ServeCodec(jsonrpc2.NewServerCodec(ws, server))
}

func (ctx *chatContext) onEvent(name string, args []interface{}) error {
	return websocket.JSON.Send(ctx.socket, map[string]interface{}{
		"jsonrpc": "2.0",
		"method":  name,
		"params":  args,
	})
}

func (ctx *chatContext) onMessage(msg chatMessage) {
	ctx.onEvent("chat_message", []interface{}{msg.name, msg.text})
}
<commit_msg>Use same type for two RPC methods with same signatures.<commit_after>package main

import (
	"container/ring"
	"encoding/json"
	"errors"
	"github.com/powerman/rpc-codec/jsonrpc2"
	"golang.org/x/net/websocket"
	"net/rpc"
	"time"
)

type Context struct {
	streams map[string]*BroadcastContext
	timeout time.Duration
}

type BroadcastContext struct {
	Broadcast
	// There is a timeout after releasing a stream during which it is possible
	// to reacquire the same object and continue broadcasting. Once the timeout
	// elapses, the stream is closed for good.
	ping    chan int
	closing bool

	chatHistory *ring.Ring
	chatViewers map[*chatContext]interface{}
	chatRoster  map[string]*chatContext
}

type chatContext struct {
	name   string
	socket *websocket.Conn
	stream *BroadcastContext
}

type chatMessage struct {
	name string
	text string
}

func NewContext(timeout time.Duration) Context {
	return Context{
		timeout: timeout, streams: make(map[string]*BroadcastContext),
	}
}

// Acquire a stream for writing. Only one "writable" reference can be held;
// until it is released, this function will return an error.
func (ctx *Context) Acquire(id string) (*BroadcastContext, bool) {
	stream, ok := ctx.streams[id]

	if !ok {
		v := BroadcastContext{
			Broadcast:   NewBroadcast(),
			ping:        make(chan int),
			closing:     false,
			chatHistory: ring.New(20),
			chatViewers: make(map[*chatContext]interface{}),
			chatRoster:  make(map[string]*chatContext),
		}

		ctx.streams[id] = &v
		go ctx.closeOnRelease(id, &v)
		return &v, true
	}

	if !stream.closing {
		return nil, false
	}

	stream.closing = false
	stream.ping <- 1
	return stream, true
}

func (stream *BroadcastContext) Release() {
	stream.closing = true
	stream.ping <- 1
}

// Acquire a stream for reading. There is no limit on the number of concurrent readers.
func (ctx *Context) Get(id string) (*BroadcastContext, bool) {
	stream, ok := ctx.streams[id]
	return stream, ok
}

func (ctx *Context) closeOnRelease(id string, stream *BroadcastContext) {
	for {
		if stream.closing {
			timer := time.NewTimer(ctx.timeout)

			select {
			case <-stream.ping:
				timer.Stop()
			case <-timer.C:
				delete(ctx.streams, id)
				stream.Close()
				return
			}
		} else {
			<-stream.ping
		}
	}
}

type RPCSingleStringArg struct {
	First string
}

func (x *RPCSingleStringArg) UnmarshalJSON(buf []byte) error {
	fields := []interface{}{&x.First}
	expect := len(fields)
	if err := json.Unmarshal(buf, &fields); err != nil {
		return err
	}
	if len(fields) != expect {
		return errors.New("invalid number of arguments")
	}
	return nil
}

func (ctx *chatContext) SetName(args *RPCSingleStringArg, _ *interface{}) error {
	name := args.First
	// TODO check that the name is alphanumeric
	// TODO check that the name is not too long
	if _, ok := ctx.stream.chatRoster[name]; ok {
		return errors.New("name already taken")
	}

	ctx.stream.chatRoster[name] = ctx
	if ctx.name != "" {
		delete(ctx.stream.chatRoster, ctx.name)
	}
	ctx.name = name
	return nil
}

func (ctx *chatContext) SendMessage(args *RPCSingleStringArg, _ *interface{}) error {
	// TODO check that the message is not whitespace-only
	// TODO check that the message is not too long
	if ctx.name == "" {
		return errors.New("must obtain a name first")
	}

	msg := chatMessage{ctx.name, args.First}

	for viewer := range ctx.stream.chatViewers {
		viewer.onMessage(msg)
	}

	ctx.stream.chatHistory.Value = msg
	ctx.stream.chatHistory = ctx.stream.chatHistory.Next()
	return nil
}

func (ctx *chatContext) RequestHistory(_ *interface{}, _ *interface{}) error {
	r := ctx.stream.chatHistory

	for i := 0; i < r.Len(); i++ {
		if r.Value != nil {
			ctx.onMessage(r.Value.(chatMessage))
		}
		r = r.Next()
	}

	return nil
}

func (ctx *chatContext) onEvent(name string, args []interface{}) error {
	return websocket.JSON.Send(ctx.socket, map[string]interface{}{
		"jsonrpc": "2.0",
		"method":  name,
		"params":  args,
	})
}

func (ctx *chatContext) onMessage(msg chatMessage) {
	ctx.onEvent("chat_message", []interface{}{msg.name, msg.text})
}

func (stream *BroadcastContext) RunRPC(ws *websocket.Conn) {
	chatter := chatContext{name: "", socket: ws, stream: stream}

	stream.chatViewers[&chatter] = nil
	defer func() {
		delete(stream.chatViewers, &chatter)

		if chatter.name != "" {
			delete(stream.chatRoster, chatter.name)
		}
	}()

	server := rpc.NewServer()
	server.RegisterName("Chat", &chatter)
	server.ServeCodec(jsonrpc2.NewServerCodec(ws, server))
}
<|endoftext|>