<commit_before>package rest

import (
	"code.google.com/p/goprotobuf/proto"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"github.com/qorio/omni/api"
	"github.com/qorio/omni/auth"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

var (
	ErrMissingInput                 = errors.New("error-missing-input")
	ErrUnknownContentType           = errors.New("error-no-content-type")
	ErrUnknownMethod                = errors.New("error-unknown-method")
	ErrIncompatibleType             = errors.New("error-incompatible-type")
	ErrNotSupportedUrlParameterType = errors.New("error-not-supported-url-query-param-type")
	ErrNoHttpHeaderSpec             = errors.New("error-no-http-header-spec")
)

var (
	json_marshaler = func(contentType string, resp http.ResponseWriter, typed interface{}) error {
		if buff, err := json.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	json_unmarshaler = func(body io.ReadCloser, typed interface{}) error {
		dec := json.NewDecoder(body)
		return dec.Decode(typed)
	}

	proto_marshaler = func(contentType string, resp http.ResponseWriter, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		if buff, err := proto.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	proto_unmarshaler = func(body io.ReadCloser, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		buff, err := ioutil.ReadAll(body)
		if err != nil {
			return err
		}
		return proto.Unmarshal(buff, typed)
	}

	marshalers = map[string]func(string, http.ResponseWriter, interface{}) error{
		"":                     json_marshaler,
		"application/json":     json_marshaler,
		"application/protobuf": proto_marshaler,
	}

	unmarshalers = map[string]func(io.ReadCloser, interface{}) error{
		"":                     json_unmarshaler,
		"application/json":     json_unmarshaler,
		"application/protobuf": proto_unmarshaler,
	}
)

type Handler func(http.ResponseWriter, *http.Request)

type ServiceMethodImpl struct {
	Api                  api.MethodSpec // note this is by copy -- so that behavior is deterministic after initialization
	Handler              Handler
	AuthenticatedHandler auth.HttpHandler
	ServiceId            string
}

func SetHandler(m api.MethodSpec, h Handler) *ServiceMethodImpl {
	if m.AuthScope != "" {
		panic(errors.New(fmt.Sprintf("Method %s has oauth scopes but binding to unauthed handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:     m,
		Handler: h,
	}
}

func SetAuthenticatedHandler(serviceId string, m api.MethodSpec, h auth.HttpHandler) *ServiceMethodImpl {
	if m.AuthScope == "" {
		panic(errors.New(fmt.Sprintf("Method %s has no oauth scopes but binding to authenticated handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:                  m,
		AuthenticatedHandler: h,
		ServiceId:            serviceId,
	}
}

type EngineEvent struct {
	Domain        string
	Service       string
	ServiceMethod api.ServiceMethod
	Body          interface{}
}

type Engine interface {
	Bind(...*ServiceMethodImpl)
	ServeHTTP(http.ResponseWriter, *http.Request)
	GetUrlParameter(*http.Request, string) string
	GetHttpHeaders(*http.Request, api.HttpHeaders) (map[string][]string, error)
	GetUrlQueries(*http.Request, api.UrlQueries) (api.UrlQueries, error)
	Unmarshal(*http.Request, proto.Message) error
	Marshal(*http.Request, proto.Message, http.ResponseWriter) error
	UnmarshalJSON(*http.Request, interface{}) error
	MarshalJSON(*http.Request, interface{}, http.ResponseWriter) error
	HandleError(http.ResponseWriter, *http.Request, string, int) error
	EventChannel() chan<- *EngineEvent
}

type engine struct {
	spec       *api.ServiceMethods
	router     *mux.Router
	auth       auth.Service
	event_chan chan *EngineEvent
	done_chan  chan bool
	webhooks   WebhookManager
}

func NewEngine(spec *api.ServiceMethods, auth auth.Service, webhooks WebhookManager) *engine {
	e := &engine{
		spec:       spec,
		router:     mux.NewRouter(),
		auth:       auth,
		event_chan: make(chan *EngineEvent),
		done_chan:  make(chan bool),
		webhooks:   webhooks,
	}
	return e
}

func (this *engine) Router() *mux.Router {
	return this.router
}

func (this *engine) ServeHTTP(resp http.ResponseWriter, request *http.Request) {
	// Also start listening on the event channel for any webhook calls
	go func() {
		for {
			select {

			case message := <-this.event_chan:
				this.do_callback(message)

			case done := <-this.done_chan:
				if done {
					glog.Infoln("REST engine event channel stopped.")
					return
				}
			}
		}
	}()

	this.router.ServeHTTP(resp, request)
}

func (this *engine) GetUrlParameter(req *http.Request, key string) string {
	vars := mux.Vars(req)
	if val, has := vars[key]; has {
		return val
	} else if err := req.ParseForm(); err == nil {
		if _, has := req.Form[key]; has {
			return req.Form[key][0]
		}
	}
	return ""
}

func (this *engine) GetHttpHeaders(req *http.Request, m api.HttpHeaders) (map[string][]string, error) {
	if m == nil {
		return nil, ErrNoHttpHeaderSpec
	}
	q := make(map[string][]string)
	for k, h := range m {
		if l, ok := req.Header[h]; ok {
			// Really strange -- you can have a 1 element list with value that's actually comma-delimited.
			if len(l) == 1 {
				q[k] = strings.Split(l[0], ", ")
			} else {
				q[k] = l
			}
		}
	}
	return q, nil
}

func (this *engine) GetUrlQueries(req *http.Request, m api.UrlQueries) (api.UrlQueries, error) {
	result := make(api.UrlQueries)
	for key, default_value := range m {
		actual := this.GetUrlParameter(req, key)
		if actual != "" {
			// Check the type and do conversion
			switch reflect.TypeOf(default_value).Kind() {
			case reflect.Bool:
				if v, err := strconv.ParseBool(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.String:
				result[key] = actual
			case reflect.Int:
				if v, err := strconv.Atoi(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float32:
				if v, err := strconv.ParseFloat(actual, 32); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float64:
				if v, err := strconv.ParseFloat(actual, 64); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			default:
				return nil, ErrNotSupportedUrlParameterType
			}

		} else {
			result[key] = default_value
		}
	}
	return result, nil
}

func (this *engine) Bind(endpoints ...*ServiceMethodImpl) {
	for i, ep := range endpoints {
		switch {
		case ep.Handler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute, ep.Handler).Methods(string(ep.Api.HttpMethod))

		case ep.AuthenticatedHandler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute,
				this.auth.RequiresAuth(ep.Api.AuthScope, func(token *auth.Token) []string {
					return strings.Split(token.GetString(ep.ServiceId+"/@scopes"), ",")
				}, ep.AuthenticatedHandler)).Methods(string(ep.Api.HttpMethod))

		case ep.Handler == nil && ep.AuthenticatedHandler == nil:
			panic(errors.New(fmt.Sprintf("No implementation for REST endpoint[%d]: %s", i, ep)))
		}

		// check the content type
		for _, ct := range ep.Api.ContentTypes {
			if _, has := marshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
			if _, has := unmarshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
		}
	}
}

func content_type_for_request(req *http.Request) string {
	t := "application/json"

	if req.Method == "POST" || req.Method == "PUT" {
		t = req.Header.Get("Content-Type")
	}
	switch t {
	case "*/*":
		return "application/json"
	case "":
		return "application/json"
	default:
		return t
	}
}

func content_type_for_response(req *http.Request) string {
	t := req.Header.Get("Accept")
	switch t {
	case "*/*":
		return "application/json"
	case "":
		return "application/json"
	default:
		return t
	}
}

var ErrorRenderer = func(resp http.ResponseWriter, req *http.Request, message string, code int) (err error) {
	// First look for accept content type in the header
	ct := content_type_for_response(req)
	switch ct {
	case "application/json":
		resp.Write([]byte(fmt.Sprintf("{ \"error\": \"%s\" }", message)))
		return
	case "application/protobuf":
		return
	default:
		resp.Write([]byte(fmt.Sprintf("Error: %s ", message)))
		return
	}
}

func (this *engine) Unmarshal(req *http.Request, typed proto.Message) (err error) {
	contentType := content_type_for_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) Marshal(req *http.Request, typed proto.Message, resp http.ResponseWriter) (err error) {
	contentType := content_type_for_response(req)
	if marshaler, has := marshalers[contentType]; has {
		return marshaler(contentType, resp, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) UnmarshalJSON(req *http.Request, any interface{}) (err error) {
	contentType := content_type_for_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, any)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) MarshalJSON(req *http.Request, any interface{}, resp http.ResponseWriter) (err error) {
	if buff, err := json.Marshal(any); err == nil {
		resp.Header().Add("Content-Type", "application/json")
		resp.Write(buff)
		return nil
	} else {
		return err
	}
}

func (this *engine) HandleError(resp http.ResponseWriter, req *http.Request, message string, code int) (err error) {
	resp.WriteHeader(code)
	resp.Write([]byte(fmt.Sprintf("{\"error\":\"%s\"}", message)))
	return
}

func (this *engine) EventChannel() chan<- *EngineEvent {
	return this.event_chan
}

func (this *engine) do_callback(message *EngineEvent) error {
	if this.webhooks == nil {
		return nil
	}
	if m, has := (*this.spec)[message.ServiceMethod]; has {
		if m.CallbackEvent != api.EventKey("") {
			return this.webhooks.Send(message.Domain, message.Service, string(m.CallbackEvent), message.Body, m.CallbackBodyTemplate)
		}
	}
	return ErrUnknownMethod
}
<commit_msg>Function to check if request content type is JSON<commit_after>package rest

import (
	"code.google.com/p/goprotobuf/proto"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"github.com/qorio/omni/api"
	"github.com/qorio/omni/auth"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

var (
	ErrMissingInput                 = errors.New("error-missing-input")
	ErrUnknownContentType           = errors.New("error-no-content-type")
	ErrUnknownMethod                = errors.New("error-unknown-method")
	ErrIncompatibleType             = errors.New("error-incompatible-type")
	ErrNotSupportedUrlParameterType = errors.New("error-not-supported-url-query-param-type")
	ErrNoHttpHeaderSpec             = errors.New("error-no-http-header-spec")
)

var (
	json_marshaler = func(contentType string, resp http.ResponseWriter, typed interface{}) error {
		if buff, err := json.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	json_unmarshaler = func(body io.ReadCloser, typed interface{}) error {
		dec := json.NewDecoder(body)
		return dec.Decode(typed)
	}

	proto_marshaler = func(contentType string, resp http.ResponseWriter, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		if buff, err := proto.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	proto_unmarshaler = func(body io.ReadCloser, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		buff, err := ioutil.ReadAll(body)
		if err != nil {
			return err
		}
		return proto.Unmarshal(buff, typed)
	}

	marshalers = map[string]func(string, http.ResponseWriter, interface{}) error{
		"":                     json_marshaler,
		"application/json":     json_marshaler,
		"application/protobuf": proto_marshaler,
	}

	unmarshalers = map[string]func(io.ReadCloser, interface{}) error{
		"":                     json_unmarshaler,
		"application/json":     json_unmarshaler,
		"application/protobuf": proto_unmarshaler,
	}
)

type Handler func(http.ResponseWriter, *http.Request)

type ServiceMethodImpl struct {
	Api                  api.MethodSpec // note this is by copy -- so that behavior is deterministic after initialization
	Handler              Handler
	AuthenticatedHandler auth.HttpHandler
	ServiceId            string
}

func SetHandler(m api.MethodSpec, h Handler) *ServiceMethodImpl {
	if m.AuthScope != "" {
		panic(errors.New(fmt.Sprintf("Method %s has oauth scopes but binding to unauthed handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:     m,
		Handler: h,
	}
}

func SetAuthenticatedHandler(serviceId string, m api.MethodSpec, h auth.HttpHandler) *ServiceMethodImpl {
	if m.AuthScope == "" {
		panic(errors.New(fmt.Sprintf("Method %s has no oauth scopes but binding to authenticated handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:                  m,
		AuthenticatedHandler: h,
		ServiceId:            serviceId,
	}
}

type EngineEvent struct {
	Domain        string
	Service       string
	ServiceMethod api.ServiceMethod
	Body          interface{}
}

type Engine interface {
	Bind(...*ServiceMethodImpl)
	ServeHTTP(http.ResponseWriter, *http.Request)
	GetUrlParameter(*http.Request, string) string
	GetHttpHeaders(*http.Request, api.HttpHeaders) (map[string][]string, error)
	GetUrlQueries(*http.Request, api.UrlQueries) (api.UrlQueries, error)
	Unmarshal(*http.Request, proto.Message) error
	Marshal(*http.Request, proto.Message, http.ResponseWriter) error
	UnmarshalJSON(*http.Request, interface{}) error
	MarshalJSON(*http.Request, interface{}, http.ResponseWriter) error
	HandleError(http.ResponseWriter, *http.Request, string, int) error
	EventChannel() chan<- *EngineEvent
}

type engine struct {
	spec       *api.ServiceMethods
	router     *mux.Router
	auth       auth.Service
	event_chan chan *EngineEvent
	done_chan  chan bool
	webhooks   WebhookManager
}

func NewEngine(spec *api.ServiceMethods, auth auth.Service, webhooks WebhookManager) *engine {
	e := &engine{
		spec:       spec,
		router:     mux.NewRouter(),
		auth:       auth,
		event_chan: make(chan *EngineEvent),
		done_chan:  make(chan bool),
		webhooks:   webhooks,
	}
	return e
}

func (this *engine) Router() *mux.Router {
	return this.router
}

func (this *engine) ServeHTTP(resp http.ResponseWriter, request *http.Request) {
	// Also start listening on the event channel for any webhook calls
	go func() {
		for {
			select {

			case message := <-this.event_chan:
				this.do_callback(message)

			case done := <-this.done_chan:
				if done {
					glog.Infoln("REST engine event channel stopped.")
					return
				}
			}
		}
	}()

	this.router.ServeHTTP(resp, request)
}

func (this *engine) GetUrlParameter(req *http.Request, key string) string {
	vars := mux.Vars(req)
	if val, has := vars[key]; has {
		return val
	} else if err := req.ParseForm(); err == nil {
		if _, has := req.Form[key]; has {
			return req.Form[key][0]
		}
	}
	return ""
}

func (this *engine) GetHttpHeaders(req *http.Request, m api.HttpHeaders) (map[string][]string, error) {
	if m == nil {
		return nil, ErrNoHttpHeaderSpec
	}
	q := make(map[string][]string)
	for k, h := range m {
		if l, ok := req.Header[h]; ok {
			// Really strange -- you can have a 1 element list with value that's actually comma-delimited.
			if len(l) == 1 {
				q[k] = strings.Split(l[0], ", ")
			} else {
				q[k] = l
			}
		}
	}
	return q, nil
}

func (this *engine) GetUrlQueries(req *http.Request, m api.UrlQueries) (api.UrlQueries, error) {
	result := make(api.UrlQueries)
	for key, default_value := range m {
		actual := this.GetUrlParameter(req, key)
		if actual != "" {
			// Check the type and do conversion
			switch reflect.TypeOf(default_value).Kind() {
			case reflect.Bool:
				if v, err := strconv.ParseBool(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.String:
				result[key] = actual
			case reflect.Int:
				if v, err := strconv.Atoi(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float32:
				if v, err := strconv.ParseFloat(actual, 32); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float64:
				if v, err := strconv.ParseFloat(actual, 64); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			default:
				return nil, ErrNotSupportedUrlParameterType
			}

		} else {
			result[key] = default_value
		}
	}
	return result, nil
}

func (this *engine) Bind(endpoints ...*ServiceMethodImpl) {
	for i, ep := range endpoints {
		switch {
		case ep.Handler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute, ep.Handler).Methods(string(ep.Api.HttpMethod))

		case ep.AuthenticatedHandler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute,
				this.auth.RequiresAuth(ep.Api.AuthScope, func(token *auth.Token) []string {
					return strings.Split(token.GetString(ep.ServiceId+"/@scopes"), ",")
				}, ep.AuthenticatedHandler)).Methods(string(ep.Api.HttpMethod))

		case ep.Handler == nil && ep.AuthenticatedHandler == nil:
			panic(errors.New(fmt.Sprintf("No implementation for REST endpoint[%d]: %s", i, ep)))
		}

		// check the content type
		for _, ct := range ep.Api.ContentTypes {
			if _, has := marshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
			if _, has := unmarshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
		}
	}
}

func JSONContentType(req *http.Request) bool {
	return "application/json" == content_type_for_request(req)
}

func content_type_for_request(req *http.Request) string {
	t := "application/json"

	if req.Method == "POST" || req.Method == "PUT" {
		t = req.Header.Get("Content-Type")
	}
	switch t {
	case "*/*":
		return "application/json"
	case "":
		return "application/json"
	default:
		return t
	}
}

func content_type_for_response(req *http.Request) string {
	t := req.Header.Get("Accept")
	switch t {
	case "*/*":
		return "application/json"
	case "":
		return "application/json"
	default:
		return t
	}
}

var ErrorRenderer = func(resp http.ResponseWriter, req *http.Request, message string, code int) (err error) {
	// First look for accept content type in the header
	ct := content_type_for_response(req)
	switch ct {
	case "application/json":
		resp.Write([]byte(fmt.Sprintf("{ \"error\": \"%s\" }", message)))
		return
	case "application/protobuf":
		return
	default:
		resp.Write([]byte(fmt.Sprintf("Error: %s ", message)))
		return
	}
}

func (this *engine) Unmarshal(req *http.Request, typed proto.Message) (err error) {
	contentType := content_type_for_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) Marshal(req *http.Request, typed proto.Message, resp http.ResponseWriter) (err error) {
	contentType := content_type_for_response(req)
	if marshaler, has := marshalers[contentType]; has {
		return marshaler(contentType, resp, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) UnmarshalJSON(req *http.Request, any interface{}) (err error) {
	contentType := content_type_for_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, any)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) MarshalJSON(req *http.Request, any interface{}, resp http.ResponseWriter) (err error) {
	if buff, err := json.Marshal(any); err == nil {
		resp.Header().Add("Content-Type", "application/json")
		resp.Write(buff)
		return nil
	} else {
		return err
	}
}

func (this *engine) HandleError(resp http.ResponseWriter, req *http.Request, message string, code int) (err error) {
	resp.WriteHeader(code)
	resp.Write([]byte(fmt.Sprintf("{\"error\":\"%s\"}", message)))
	return
}

func (this *engine) EventChannel() chan<- *EngineEvent {
	return this.event_chan
}

func (this *engine) do_callback(message *EngineEvent) error {
	if this.webhooks == nil {
		return nil
	}
	if m, has := (*this.spec)[message.ServiceMethod]; has {
		if m.CallbackEvent != api.EventKey("") {
			return this.webhooks.Send(message.Domain, message.Service, string(m.CallbackEvent), message.Body, m.CallbackBodyTemplate)
		}
	}
	return ErrUnknownMethod
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"encoding/json"
	"net"
	"time"

	"golang.org/x/net/ipv4"
	"gopkg.in/cheggaaa/pb.v1"
)

type TokenCookie struct {
	Host  string `json:"host"`
	Time  string `json:"time"`
	Token string `json:"token"`
	DSCP  int    `json:"dscp"`
}

type DSCP struct {
	Name string
	Value int
}

func main() {
	url := "http://trace.enoti.me/start"

	dscp_map := [21]DSCP{
		{Name:"BE",   Value:0x00},
		{Name:"EF",   Value:0x2e},
		{Name:"CS1",  Value:0x08},
		{Name:"CS2",  Value:0x10},
		{Name:"CS3",  Value:0x18},
		{Name:"CS4",  Value:0x20},
		{Name:"CS5",  Value:0x28},
		{Name:"CS6",  Value:0x30},
		{Name:"CS7",  Value:0x38},
		{Name:"AF11", Value:0x0a},
		{Name:"AF12", Value:0x0c},
		{Name:"AF13", Value:0x0e},
		{Name:"AF21", Value:0x12},
		{Name:"AF22", Value:0x14},
		{Name:"AF23", Value:0x16},
		{Name:"AF31", Value:0x1a},
		{Name:"AF32", Value:0x1c},
		{Name:"AF33", Value:0x1e},
		{Name:"AF41", Value:0x22},
		{Name:"AF42", Value:0x24},
		{Name:"AF43", Value:0x26},
	}

	fmt.Printf("connecting to %s .  .  . ", url)
	res, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf(". . ... done\n")
	data, err := ioutil.ReadAll(res.Body)

	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var token TokenCookie
	err = json.Unmarshal(data, &token)

	if err != nil {
		fmt.Println("error:", err)
	}

	fmt.Printf("received token:\n %+v\n", token)

	bar := pb.StartNew(10 * len(dscp_map))
	conn, err := net.Dial("udp", "trace.enoti.me:60606")

	start := time.Now()
	send_count := 10

	fmt.Println("Sending Datagrams")
    for _, mark := range dscp_map {
		token.DSCP = mark.Value

		if err := ipv4.NewConn(conn).SetTOS(token.DSCP); err != nil {
			fmt.Printf("Some error %v", err)
			return
		}

		if err != nil {
			fmt.Printf("Some error %v", err)
			return
		}

		for i := 1; i <= send_count ; i++ {
			json.NewEncoder(conn).Encode(token)
			time.Sleep(time.Millisecond * 200)
			bar.Increment()
		}

		conn.Close()
	}

	fmt.Printf("%v Datagrams representing %v DSCP Marks sent in %v\n",
		len(dscp_map) * send_count,
		len(dscp_map),
		time.Since(start))
	fmt.Println("Thank you for helping make the internet better")
}
<commit_msg>Try multiple points rather than closing the connection<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"encoding/json"
	"net"
	"time"

	"golang.org/x/net/ipv4"
	"gopkg.in/cheggaaa/pb.v1"
)

type TokenCookie struct {
	Host  string `json:"host"`
	Time  string `json:"time"`
	Token string `json:"token"`
	DSCP  int    `json:"dscp"`
}

type DSCP struct {
	Name string
	Value int
}

func main() {
	url := "http://trace.enoti.me/start"

	dscp_map := [21]DSCP{
		{Name:"BE",   Value:0x00},
		{Name:"EF",   Value:0x2e},
		{Name:"CS1",  Value:0x08},
		{Name:"CS2",  Value:0x10},
		{Name:"CS3",  Value:0x18},
		{Name:"CS4",  Value:0x20},
		{Name:"CS5",  Value:0x28},
		{Name:"CS6",  Value:0x30},
		{Name:"CS7",  Value:0x38},
		{Name:"AF11", Value:0x0a},
		{Name:"AF12", Value:0x0c},
		{Name:"AF13", Value:0x0e},
		{Name:"AF21", Value:0x12},
		{Name:"AF22", Value:0x14},
		{Name:"AF23", Value:0x16},
		{Name:"AF31", Value:0x1a},
		{Name:"AF32", Value:0x1c},
		{Name:"AF33", Value:0x1e},
		{Name:"AF41", Value:0x22},
		{Name:"AF42", Value:0x24},
		{Name:"AF43", Value:0x26},
	}

	fmt.Printf("connecting to %s .  .  . ", url)
	res, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf(". . ... done\n")
	data, err := ioutil.ReadAll(res.Body)

	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	var token TokenCookie
	err = json.Unmarshal(data, &token)

	if err != nil {
		fmt.Println("error:", err)
	}

	fmt.Printf("received token:\n %+v\n", token)

	conn, err := net.Dial("udp", "trace.enoti.me:60606")

	start := time.Now()
	send_count := 10

	fmt.Println("Sending Datagrams")
	bar := pb.StartNew(10 * len(dscp_map))
    for _, mark := range dscp_map {
		token.DSCP = mark.Value

		if err := ipv4.NewConn(conn).SetTOS(token.DSCP); err != nil {
			fmt.Printf("Some error %v", err)
			return
		}

		if err != nil {
			fmt.Printf("Some error %v", err)
			return
		}

		for i := 1; i <= send_count ; i++ {
			json.NewEncoder(conn).Encode(token)
			time.Sleep(time.Millisecond * 200)
			bar.Increment()
		}

	}
	conn.Close()

	fmt.Printf("%v Datagrams representing %v DSCP Marks sent in %v\n",
		len(dscp_map) * send_count,
		len(dscp_map),
		time.Since(start))
	fmt.Println("Thank you for helping make the internet better")
}
<|endoftext|>


<commit_before>package gridas

import (
	"fmt"
	"net/http"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"gridas/config"
	"gridas/mylog"
)

//Listener is responsible for receiving requests and storing them in PetitionStore.
//It then passes a reference to the object Petition which wraps the original HTTP request through the channel Sendto,
//where the Consumer should collected it for further processing
type Listener struct {
	//Channel for sending petitions
	SendTo chan<- *Petition
	//Configuration object
	Cfg *config.Config
	//Flag signaling listener should finish
	stopping bool
	//Session seed for mongo
	SessionSeed *mgo.Session
}

//ServeHTTP implements HTTP handler interface
func (l *Listener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	mylog.Debugf("received request %+v", r)
	if l.stopping {
		mylog.Debug("warning client server is stopping")
		http.Error(w, "Server is shutting down", 503)
		return
	}
	relayedRequest, e := newPetition(r)
	if e != nil {
		mylog.Debug("petition with error", e)
		http.Error(w, e.Error(), 400)
		return
	}
	if l.SessionSeed == nil {
		mylog.Alert("listener session seed is nil")
		http.Error(w, "Contact support", 500)
		return
	}
	relayedRequest.Session = l.SessionSeed.New()
	relayedRequest.Session.SetMode(mgo.Monotonic, true)
	db := relayedRequest.Session.DB(l.Cfg.Database)
	petColl := db.C(l.Cfg.Instance + l.Cfg.PetitionsColl)
	mylog.Debugf("petition created %+v", relayedRequest)
	e = petColl.Insert(relayedRequest)
	if e != nil {
		http.Error(w, relayedRequest.ID, 500)
		mylog.Alert("ERROR inserting", relayedRequest.ID, e)
		relayedRequest.Session.Close()
		relayedRequest.Session = nil
		return
	}
	select {
	case l.SendTo <- relayedRequest:
		mylog.Debug("enqueued petition", relayedRequest)
		fmt.Fprintln(w, relayedRequest.ID)
	default:
		mylog.Alert("server is busy")
		http.Error(w, "Server is busy", 500)
		mylog.Debugf("before remove petition", relayedRequest.ID)
		err := petColl.Remove(bson.M{"id": relayedRequest.ID})
		mylog.Debugf("after remove petition", relayedRequest.ID)
		if err != nil {
			mylog.Alert("ERROR removing petition", relayedRequest.ID, e)
		}
		relayedRequest.Session.Close()
		relayedRequest.Session = nil
		return
	}
}

//Stop asks listener to stop receiving petitions
func (l *Listener) Stop() {
	mylog.Debug("listener received stop")
	l.stopping = true //Risky??
}
<commit_msg>Do not create a new session<commit_after>package gridas

import (
	"fmt"
	"net/http"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"gridas/config"
	"gridas/mylog"
)

//Listener is responsible for receiving requests and storing them in PetitionStore.
//It then passes a reference to the object Petition which wraps the original HTTP request through the channel Sendto,
//where the Consumer should collected it for further processing
type Listener struct {
	//Channel for sending petitions
	SendTo chan<- *Petition
	//Configuration object
	Cfg *config.Config
	//Flag signaling listener should finish
	stopping bool
	//Session seed for mongo
	SessionSeed *mgo.Session
}

//ServeHTTP implements HTTP handler interface
func (l *Listener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	mylog.Debugf("received request %+v", r)
	if l.stopping {
		mylog.Debug("warning client server is stopping")
		http.Error(w, "Server is shutting down", 503)
		return
	}
	relayedRequest, e := newPetition(r)
	if e != nil {
		mylog.Debug("petition with error", e)
		http.Error(w, e.Error(), 400)
		return
	}
	relayedRequest.Session = l.SessionSeed.Clone()
	relayedRequest.Session.SetMode(mgo.Monotonic, true)
	db := relayedRequest.Session.DB(l.Cfg.Database)
	petColl := db.C(l.Cfg.Instance + l.Cfg.PetitionsColl)
	mylog.Debugf("petition created %+v", relayedRequest)
	e = petColl.Insert(relayedRequest)
	if e != nil {
		http.Error(w, relayedRequest.ID, 500)
		mylog.Alert("ERROR inserting", relayedRequest.ID, e)
		relayedRequest.Session.Close()
		relayedRequest.Session = nil
		return
	}
	select {
	case l.SendTo <- relayedRequest:
		mylog.Debug("enqueued petition", relayedRequest)
		fmt.Fprintln(w, relayedRequest.ID)
	default:
		mylog.Alert("server is busy")
		http.Error(w, "Server is busy", 500)
		mylog.Debugf("before remove petition", relayedRequest.ID)
		err := petColl.Remove(bson.M{"id": relayedRequest.ID})
		mylog.Debugf("after remove petition", relayedRequest.ID)
		if err != nil {
			mylog.Alert("ERROR removing petition", relayedRequest.ID, e)
		}
		relayedRequest.Session.Close()
		relayedRequest.Session = nil
		return
	}
}

//Stop asks listener to stop receiving petitions
func (l *Listener) Stop() {
	mylog.Debug("listener received stop")
	l.stopping = true //Risky??
}
<|endoftext|>


<commit_before>package rabbitmq

import (
	"github.com/streadway/amqp"
)

type Consumer struct {
	*RabbitMQ
	deliveries <-chan amqp.Delivery
	handler    func(amqp.Delivery)
	done       chan error
	session    Session
}

func (c *Consumer) Deliveries() <-chan amqp.Delivery {
	return c.deliveries
}

// This is a constructor for consumer creation
// Accepts Exchange, Queue, BindingOptions and ConsumerOptions
func NewConsumer(e Exchange, q Queue, bo BindingOptions, co ConsumerOptions) (*Consumer, error) {
	rmq, err := newRabbitMQConnection(co.Tag)
	if err != nil {
		return nil, err
	}

	c := &Consumer{
		RabbitMQ: rmq,
		done:     make(chan error),
		session: Session{
			Exchange:        e,
			Queue:           q,
			ConsumerOptions: co,
			BindingOptions:  bo,
		},
	}

	err = c.connect()
	if err != nil {
		return nil, err
	}

	return c, nil
}

func (c *Consumer) connect() error {
	e := c.session.Exchange
	q := c.session.Queue
	bo := c.session.BindingOptions
	co := c.session.ConsumerOptions

	var err error

	// declaring Exchange
	if err = c.channel.ExchangeDeclare(
		e.Name,       // name of the exchange
		e.Type,       // type
		e.Durable,    // durable
		e.AutoDelete, // delete when complete
		e.Internal,   // internal
		e.NoWait,     // noWait
		e.Args,       // arguments
	); err != nil {
		return err
	}

	// declaring Queue
	queue, err := c.channel.QueueDeclare(
		q.Name,       // name of the queue
		q.Durable,    // durable
		q.AutoDelete, // delete when usused
		q.Exclusive,  // exclusive
		q.NoWait,     // noWait
		q.Args,       // arguments
	)
	if err != nil {
		return err
	}

	// binding Exchange to Queue
	if err = c.channel.QueueBind(
		// bind to real queue
		queue.Name,    // name of the queue
		bo.RoutingKey, // bindingKey
		e.Name,        // sourceExchange
		bo.NoWait,     // noWait
		bo.Args,       // arguments
	); err != nil {
		return err
	}

	// Exchange bound to Queue, starting Consume
	deliveries, err := c.channel.Consume(
		// consume from real queue
		queue.Name,   // name
		co.Tag,       // consumerTag,
		co.AutoAck,   // autoAck
		co.Exclusive, // exclusive
		co.NoLocal,   // noLocal
		co.NoWait,    // noWait
		co.Args,      // arguments
	)
	if err != nil {
		return err
	}

	// should we stop streaming, in order not to consume from server?
	c.deliveries = deliveries

	return nil
}

// Accepts a handler function for every message streamed from RabbitMq
// will be called within this handler func
func (c *Consumer) Consume(handler func(delivery amqp.Delivery)) {
	c.handler = handler

	// handle all consumer errors, if required re-connect
	// there are problems with reconnection logic for now
	for delivery := range c.deliveries {
		handler(delivery)
	}

	// change fmt -> log
	log.Info("handle: deliveries channel closed")
	c.done <- nil
}

// Gracefully close all connections and wait
// for handler to finish its, messages
func (c *Consumer) Shutdown() error {
	// to-do
	// first stop streaming then close connections
	err := shutdown(c.conn, c.channel, c.tag)
	if err != nil {
		return nil
	}
	// change fmt -> log
	defer log.Info("Consumer shutdown OK")
	log.Info("Waiting for handler to exit")

	// this channel is here for finishing the consumer's ranges of
	// delivery chans.  We need every delivery to be processed, here make
	// sure to wait for all consumers goroutines to finish before exiting our
	// process.
	return <-c.done
}

// implementing closer interface
func (c *Consumer) RegisterSignalHandler() {
	registerSignalHandler(c)
}
<commit_msg>[RabbitMQ]  remove obsolote doc<commit_after>package rabbitmq

import (
	"github.com/streadway/amqp"
)

type Consumer struct {
	*RabbitMQ
	deliveries <-chan amqp.Delivery
	handler    func(amqp.Delivery)
	done       chan error
	session    Session
}

func (c *Consumer) Deliveries() <-chan amqp.Delivery {
	return c.deliveries
}

// This is a constructor for consumer creation
// Accepts Exchange, Queue, BindingOptions and ConsumerOptions
func NewConsumer(e Exchange, q Queue, bo BindingOptions, co ConsumerOptions) (*Consumer, error) {
	rmq, err := newRabbitMQConnection(co.Tag)
	if err != nil {
		return nil, err
	}

	c := &Consumer{
		RabbitMQ: rmq,
		done:     make(chan error),
		session: Session{
			Exchange:        e,
			Queue:           q,
			ConsumerOptions: co,
			BindingOptions:  bo,
		},
	}

	err = c.connect()
	if err != nil {
		return nil, err
	}

	return c, nil
}

func (c *Consumer) connect() error {
	e := c.session.Exchange
	q := c.session.Queue
	bo := c.session.BindingOptions
	co := c.session.ConsumerOptions

	var err error

	// declaring Exchange
	if err = c.channel.ExchangeDeclare(
		e.Name,       // name of the exchange
		e.Type,       // type
		e.Durable,    // durable
		e.AutoDelete, // delete when complete
		e.Internal,   // internal
		e.NoWait,     // noWait
		e.Args,       // arguments
	); err != nil {
		return err
	}

	// declaring Queue
	queue, err := c.channel.QueueDeclare(
		q.Name,       // name of the queue
		q.Durable,    // durable
		q.AutoDelete, // delete when usused
		q.Exclusive,  // exclusive
		q.NoWait,     // noWait
		q.Args,       // arguments
	)
	if err != nil {
		return err
	}

	// binding Exchange to Queue
	if err = c.channel.QueueBind(
		// bind to real queue
		queue.Name,    // name of the queue
		bo.RoutingKey, // bindingKey
		e.Name,        // sourceExchange
		bo.NoWait,     // noWait
		bo.Args,       // arguments
	); err != nil {
		return err
	}

	// Exchange bound to Queue, starting Consume
	deliveries, err := c.channel.Consume(
		// consume from real queue
		queue.Name,   // name
		co.Tag,       // consumerTag,
		co.AutoAck,   // autoAck
		co.Exclusive, // exclusive
		co.NoLocal,   // noLocal
		co.NoWait,    // noWait
		co.Args,      // arguments
	)
	if err != nil {
		return err
	}

	// should we stop streaming, in order not to consume from server?
	c.deliveries = deliveries

	return nil
}

// Accepts a handler function for every message streamed from RabbitMq
// will be called within this handler func
func (c *Consumer) Consume(handler func(delivery amqp.Delivery)) {
	c.handler = handler

	// handle all consumer errors, if required re-connect
	// there are problems with reconnection logic for now
	for delivery := range c.deliveries {
		handler(delivery)
	}

	log.Info("handle: deliveries channel closed")
	c.done <- nil
}

// Gracefully close all connections and wait
// for handler to finish its, messages
func (c *Consumer) Shutdown() error {
	// to-do
	// first stop streaming then close connections
	err := shutdown(c.conn, c.channel, c.tag)
	if err != nil {
		return nil
	}

	defer log.Info("Consumer shutdown OK")
	log.Info("Waiting for handler to exit")

	// this channel is here for finishing the consumer's ranges of
	// delivery chans.  We need every delivery to be processed, here make
	// sure to wait for all consumers goroutines to finish before exiting our
	// process.
	return <-c.done
}

// implementing closer interface
func (c *Consumer) RegisterSignalHandler() {
	registerSignalHandler(c)
}
<|endoftext|>


<commit_before>// Copyright 2015-present, Cyrill @ Schumacher.fm and the CoreStore contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"github.com/corestoreio/errors"
	"github.com/corestoreio/log"
	"github.com/corestoreio/pkg/store/scope"
)

// LeftDelim and RightDelim are used withing the core_config_data.value field to
// allow the replacement of the placeholder in exchange with the current value.
// deprecated gets not refactored
const (
	LeftDelim  = "{{"
	RightDelim = "}}"
)

// Getter implements how to receive thread-safe a configuration value from an
// underlying backend service. The provided route as an argument does not
// make any assumptions if the scope of the Path is allowed to retrieve
// the value. The NewScoped() function binds a route to a scope.Scope
// and gives you the possibility to fallback the hierarchy levels. If a value
// cannot be found, it must return false as the 2nd return argument.
type Getter interface {
	NewScoped(websiteID, storeID int64) Scoped
	Value(Path) (v Value, ok bool, err error)
}

// GetterPubSuber implements a configuration Getter and a Subscriber for Publish
// and Subscribe pattern.
type GetterPubSuber interface {
	Getter
	Subscriber
}

// Writer thread safe storing of configuration values under different paths and
// scopes.
type Writer interface {
	// Write writes a configuration entry and may return an error
	Write(p Path, value []byte) error
}

// Storager is the underlying data storage for holding the keys and its values.
// Implementations can be spf13/viper or MySQL backed. Default Storager is a
// simple mutex protected map[string]interface{}. The config.Writer function
// calls the config.Storager functions and Storager must make sure of the
// correct type conversions to the supported type of the underlying storage
// engine.
type Storager interface {
	// Set sets a key with a value and returns on success nil or
	// ErrKeyOverwritten, on failure any other error
	Set(scp scope.TypeID, path string, value []byte) error
	// Get returns the raw value `v` on success and sets `ok` to true because
	// the value has been found. If the value cannot be found for the desired
	// path, return value `ok` must be false. A nil value `v` indicates also a
	// value and hence `ok` is true, if found.
	Value(scp scope.TypeID, path string) (v []byte, ok bool, err error)
	// AllKeys returns the fully qualified keys
	AllKeys() (scps scope.TypeIDs, paths []string, err error)
}

// Service main configuration provider. Please use the NewService() function.
// TODO build in to support different environments for the same keys. E.g. different API credentials for external APIs (staging, production, etc).
type Service struct {
	// backend is the underlying data holding provider. Only access it if you
	// know exactly what you are doing.
	backend Storager

	// internal service to provide async pub/sub features while reading/writing
	// config values.
	*pubSub

	// Log can be set for debugging purpose. If nil, it panics. Default
	// log.Blackhole with disabled debug and info logging. You should use the
	// option function WithLogger because the logger gets also set to the
	// internal pub/sub service. The exported Log can be used in external
	// package to log within functional option calls. For example in
	// config/storage/ccd.
	Log log.Logger

	// TODO auto detect unmarhaller and get one from a pool pass the correct unmarshaler to the Value.
}

// NewService creates the main new configuration for all scopes: default,
// website and store. Default Storage is a simple map[string]interface{}. A new
// go routine will be startet for the publish and subscribe feature.
func NewService(backend Storager, opts ...Option) (*Service, error) {
	s := &Service{
		backend: backend,
		Log:     log.BlackHole{}, // disabled debug and info logging.
	}

	if err := s.Options(opts...); err != nil {
		if s.pubSub != nil {
			if err2 := s.Close(); err2 != nil {
				// terminate publisher go routine and prevent leaking
				return nil, errors.Wrap(err2, "[config] Service.Option.Close")
			}
		}
		return nil, errors.Wrap(err, "[config] Service.Option")
	}
	return s, nil
}

// MustNewService same as NewService but panics on error. Use only in testing
// or during boot process.
func MustNewService(backend Storager, opts ...Option) *Service {
	s, err := NewService(backend, opts...)
	if err != nil {
		panic(err)
	}
	return s
}

// Options applies service options.
func (s *Service) Options(opts ...Option) error {
	for _, opt := range opts {
		if opt != nil {
			if err := opt(s); err != nil {
				return errors.Wrap(err, "[config] Service.Options")
			}
		}
	}
	return nil
}

// NewScoped creates a new scope base configuration reader
func (s *Service) NewScoped(websiteID, storeID int64) Scoped {
	return NewScoped(s, websiteID, storeID)
}

// Write puts a value back into the Service. Example usage:
//		// Default Scope
//		p, err := config.MakeByString("currency/option/base") // or use config.MustMakeByString( ... )
// 		err := Write(p, "USD")
//
//		// Website Scope
//		// 3 for example comes from core_website/store_website database table
//		err := Write(p.Bind(scope.WebsiteID, 3), "EUR")
//
//		// Store Scope
//		// 6 for example comes from core_store/store database table
//		err := Write(p.Bind(scope.StoreID, 6), "CHF")
func (s *Service) Write(p Path, v []byte) error {
	if s.Log.IsDebug() {
		log.WhenDone(s.Log).Debug("config.Service.Write", log.Stringer("path", p), log.Int("data_length", len(v)))
	}
	if err := p.IsValid(); err != nil {
		return errors.WithStack(err)
	}

	if err := s.backend.Set(p.ScopeID, p.route, v); err != nil {
		return errors.Wrap(err, "[config] Service.backend.Set")
	}
	if s.pubSub != nil {
		s.sendMsg(p)
	}
	return nil
}

// Value returns a configuration value from the Service, ignoring the scopes
// using a direct match. Example usage:
//
//		// Default Scope
//		dp := config.MustMakePath("general/locale/timezone")
//
//		// Website Scope
//		// 3 for example comes from store_website database table
//		ws := dp.Bind(scope.WebsiteID, 3)
//
//		// Store Scope
//		// 6 for example comes from store database table
//		ss := p.Bind(scope.StoreID, 6)
func (s *Service) Value(p Path) (v Value, ok bool, err error) {
	if s.Log.IsDebug() {
		defer log.WhenDone(s.Log).Debug("config.Service.backend.Value", log.Stringer("path", p), log.Bool("found", ok), log.Err(err))
	}
	v.data, ok, err = s.backend.Value(p.ScopeID, p.route)
	return
}

// IsSet checks if a key is in the configuration. Returns false on error. Errors
// will be logged in Debug mode. Does not check if the value can be asserted to
// the desired type.
func (s *Service) IsSet(p Path) bool {
	_, ok, err := s.Value(p)
	return err == nil && ok
}

// Scoped is equal to Getter but not an interface and the underlying
// implementation takes care of providing the correct scope: default, website or
// store and bubbling up the scope chain from store -> website -> default if a
// value won't get found in the desired scope. The Path for each primitive type
// represents always a path like "section/group/element" without the scope
// string and scope ID.
//
// To restrict bubbling up you can provide a second argument scope.Scope. You
// can restrict a configuration path to be only used with the default, website
// or store scope. See the examples. This second argument will mainly be used by
// the cfgmodel package to use a defined scope in a config.Structure. If you
// access the ScopedGetter from a store.Store, store.Website type the second
// argument must already be internally pre-filled.
//
// WebsiteID and StoreID must be in a relation like enforced in the database
// tables via foreign keys. Empty storeID triggers the website scope. Empty
// websiteID and empty storeID are triggering the default scope.
//
// You can use the function NewScoped() to create a new object but not
// mandatory. Scoped must act as non-pointer value.
type Scoped struct {
	// Root holds the main functions for retrieving values by paths from the
	// storage.
	Root      Getter
	WebsiteID int64
	StoreID   int64
}

// NewScoped instantiates a ScopedGetter implementation.  Getter
// specifies the root Getter which does not know about any scope.
func NewScoped(r Getter, websiteID, storeID int64) Scoped {
	return Scoped{
		Root:      r,
		WebsiteID: websiteID,
		StoreID:   storeID,
	}
}

// IsValid checks if the object has been set up correctly.
func (ss Scoped) IsValid() bool {
	return ss.Root != nil && ((ss.WebsiteID == 0 && ss.StoreID == 0) ||
		(ss.WebsiteID > 0 && ss.StoreID == 0) ||
		(ss.WebsiteID > 0 && ss.StoreID > 0))
}

// ParentID tells you the parent underlying scope and its ID. Store falls back
// to website and website falls back to default.
func (ss Scoped) ParentID() scope.TypeID {
	if ss.StoreID > 0 {
		return scope.Website.Pack(ss.WebsiteID)
	}
	return scope.DefaultTypeID
}

// ScopeID tells you the current underlying scope and its ID to which this
// configuration has been bound to.
func (ss Scoped) ScopeID() scope.TypeID {
	if ss.StoreID > 0 {
		return scope.Store.Pack(ss.StoreID)
	}
	if ss.WebsiteID > 0 {
		return scope.Website.Pack(ss.WebsiteID)
	}
	return scope.DefaultTypeID
}

// ScopeIDs returns the hierarchical order of the scopes containing ScopeID() on
// position zero and ParentID() on position one. This function guarantees that
// the returned slice contains at least two entries.
func (ss Scoped) ScopeIDs() scope.TypeIDs {
	var ids = [2]scope.TypeID{
		ss.ScopeID(),
		ss.ParentID(),
	}
	return ids[:]
}

func (ss Scoped) isAllowedStore(restrictUpTo scope.Type) bool {
	scp := ss.ScopeID().Type()
	if restrictUpTo > scope.Absent {
		scp = restrictUpTo
	}
	return ss.StoreID > 0 && scope.PermStoreReverse.Has(scp)
}

func (ss Scoped) isAllowedWebsite(restrictUpTo scope.Type) bool {
	scp := ss.ScopeID().Type()
	if restrictUpTo > scope.Absent {
		scp = restrictUpTo
	}
	return ss.WebsiteID > 0 && scope.PermWebsiteReverse.Has(scp)
}

// Value traverses through the scopes store->website->default to find a matching
// byte slice value. The argument `restrictUpTo` scope.Type restricts the
// bubbling. For example a path gets stored in all three scopes but argument
// `restrictUpTo` specifies only website scope, then the store scope will be
// ignored for querying. If argument `restrictUpTo` has been set to zero aka.
// scope.Absent, then all three scopes are considered for querying.
func (ss Scoped) Value(restrictUpTo scope.Type, route string) (v Value, ok bool, err error) {
	// fallback to next parent scope if value does not exists
	p := Path{
		route:   route,
		ScopeID: scope.DefaultTypeID,
	}
	if ss.isAllowedStore(restrictUpTo) {
		v, ok, err := ss.Root.Value(p.BindStore(ss.StoreID))
		if ok || err != nil {
			// value found or err is not a NotFound error
			return v, ok, errors.WithStack(err)
		}
	}
	if ss.isAllowedWebsite(restrictUpTo) {
		v, ok, err := ss.Root.Value(p.BindWebsite(ss.WebsiteID))
		if ok || err != nil {
			return v, ok, errors.WithStack(err)
		}
	}
	return ss.Root.Value(p)
}
<commit_msg>service: Update docu<commit_after>// Copyright 2015-present, Cyrill @ Schumacher.fm and the CoreStore contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"github.com/corestoreio/errors"
	"github.com/corestoreio/log"
	"github.com/corestoreio/pkg/store/scope"
)

// LeftDelim and RightDelim are used withing the core_config_data.value field to
// allow the replacement of the placeholder in exchange with the current value.
// deprecated gets not refactored
const (
	LeftDelim  = "{{"
	RightDelim = "}}"
)

// Getter implements how to receive thread-safe a configuration value from an
// underlying backend service. The provided route as an argument does not
// make any assumptions if the scope of the Path is allowed to retrieve
// the value. The NewScoped() function binds a route to a scope.Scope
// and gives you the possibility to fallback the hierarchy levels. If a value
// cannot be found, it must return false as the 2nd return argument.
type Getter interface {
	NewScoped(websiteID, storeID int64) Scoped
	Value(Path) (v Value, ok bool, err error)
}

// GetterPubSuber implements a configuration Getter and a Subscriber for Publish
// and Subscribe pattern.
type GetterPubSuber interface {
	Getter
	Subscriber
}

// Writer thread safe storing of configuration values under different paths and
// scopes.
type Writer interface {
	// Write writes a configuration entry and may return an error
	Write(p Path, value []byte) error
}

// Storager is the underlying data storage for holding the keys and its values.
// Implementations can be spf13/viper or MySQL backed. Default Storager is a
// simple mutex protected in memory map[string]string.
// Storager must be safe for concurrent use.
type Storager interface {
	// Set sets a key with a value and returns on success nil or
	// ErrKeyOverwritten, on failure any other error
	Set(scp scope.TypeID, path string, value []byte) error
	// Get returns the raw value `v` on success and sets `ok` to true because
	// the value has been found. If the value cannot be found for the desired
	// path, return value `ok` must be false. A nil value `v` indicates also a
	// value and hence `ok` is true, if found.
	Value(scp scope.TypeID, path string) (v []byte, ok bool, err error)
	// AllKeys returns the fully qualified keys
	AllKeys() (scps scope.TypeIDs, paths []string, err error)
}

// Service main configuration provider. Please use the NewService() function.
// Safe for concurrent use.
// TODO build in to support different environments for the same keys. E.g. different API credentials for external APIs (staging, production, etc).
type Service struct {
	// backend is the underlying data holding provider. Only access it if you
	// know exactly what you are doing.
	backend Storager

	// internal service to provide async pub/sub features while reading/writing
	// config values.
	*pubSub

	// Log can be set for debugging purpose. If nil, it panics. Default
	// log.Blackhole with disabled debug and info logging. You should use the
	// option function WithLogger because the logger gets also set to the
	// internal pub/sub service. The exported Log can be used in external
	// package to log within functional option calls. For example in
	// config/storage/ccd.
	Log log.Logger

	// TODO auto detect unmarhaller and get one from a pool pass the correct unmarshaler to the Value.
}

// NewService creates the main new configuration for all scopes: default,
// website and store. Default Storage is a simple map[string]interface{}. A new
// go routine will be startet for the publish and subscribe feature.
func NewService(backend Storager, opts ...Option) (*Service, error) {
	s := &Service{
		backend: backend,
		Log:     log.BlackHole{}, // disabled debug and info logging.
	}

	if err := s.Options(opts...); err != nil {
		if s.pubSub != nil {
			if err2 := s.Close(); err2 != nil {
				// terminate publisher go routine and prevent leaking
				return nil, errors.Wrap(err2, "[config] Service.Option.Close")
			}
		}
		return nil, errors.Wrap(err, "[config] Service.Option")
	}
	return s, nil
}

// MustNewService same as NewService but panics on error. Use only in testing
// or during boot process.
func MustNewService(backend Storager, opts ...Option) *Service {
	s, err := NewService(backend, opts...)
	if err != nil {
		panic(err)
	}
	return s
}

// Options applies service options.
func (s *Service) Options(opts ...Option) error {
	for _, opt := range opts {
		if opt != nil {
			if err := opt(s); err != nil {
				return errors.Wrap(err, "[config] Service.Options")
			}
		}
	}
	return nil
}

// NewScoped creates a new scope base configuration reader
func (s *Service) NewScoped(websiteID, storeID int64) Scoped {
	return NewScoped(s, websiteID, storeID)
}

// Write puts a value back into the Service. Example usage:
//		// Default Scope
//		p, err := config.MakeByString("currency/option/base") // or use config.MustMakeByString( ... )
// 		err := Write(p, "USD")
//
//		// Website Scope
//		// 3 for example comes from core_website/store_website database table
//		err := Write(p.Bind(scope.WebsiteID, 3), "EUR")
//
//		// Store Scope
//		// 6 for example comes from core_store/store database table
//		err := Write(p.Bind(scope.StoreID, 6), "CHF")
func (s *Service) Write(p Path, v []byte) error {
	if s.Log.IsDebug() {
		log.WhenDone(s.Log).Debug("config.Service.Write", log.Stringer("path", p), log.Int("data_length", len(v)))
	}
	if err := p.IsValid(); err != nil {
		return errors.WithStack(err)
	}

	if err := s.backend.Set(p.ScopeID, p.route, v); err != nil {
		return errors.Wrap(err, "[config] Service.backend.Set")
	}
	if s.pubSub != nil {
		s.sendMsg(p)
	}
	return nil
}

// Value returns a configuration value from the Service, ignoring the scopes
// using a direct match. Example usage:
//
//		// Default Scope
//		dp := config.MustMakePath("general/locale/timezone")
//
//		// Website Scope
//		// 3 for example comes from store_website database table
//		ws := dp.Bind(scope.WebsiteID, 3)
//
//		// Store Scope
//		// 6 for example comes from store database table
//		ss := p.Bind(scope.StoreID, 6)
func (s *Service) Value(p Path) (v Value, ok bool, err error) {
	if s.Log.IsDebug() {
		defer log.WhenDone(s.Log).Debug("config.Service.backend.Value", log.Stringer("path", p), log.Bool("found", ok), log.Err(err))
	}
	v.data, ok, err = s.backend.Value(p.ScopeID, p.route)
	return
}

// IsSet checks if a key is in the configuration. Returns false on error. Errors
// will be logged in Debug mode. Does not check if the value can be asserted to
// the desired type.
func (s *Service) IsSet(p Path) bool {
	_, ok, err := s.Value(p)
	return err == nil && ok
}

// Scoped is equal to Getter but not an interface and the underlying
// implementation takes care of providing the correct scope: default, website or
// store and bubbling up the scope chain from store -> website -> default if a
// value won't get found in the desired scope. The Path for each primitive type
// represents always a path like "section/group/element" without the scope
// string and scope ID.
//
// To restrict bubbling up you can provide a second argument scope.Scope. You
// can restrict a configuration path to be only used with the default, website
// or store scope. See the examples. This second argument will mainly be used by
// the cfgmodel package to use a defined scope in a config.Structure. If you
// access the ScopedGetter from a store.Store, store.Website type the second
// argument must already be internally pre-filled.
//
// WebsiteID and StoreID must be in a relation like enforced in the database
// tables via foreign keys. Empty storeID triggers the website scope. Empty
// websiteID and empty storeID are triggering the default scope.
//
// You can use the function NewScoped() to create a new object but not
// mandatory. Scoped must act as non-pointer value.
type Scoped struct {
	// Root holds the main functions for retrieving values by paths from the
	// storage.
	Root      Getter
	WebsiteID int64
	StoreID   int64
}

// NewScoped instantiates a ScopedGetter implementation.  Getter
// specifies the root Getter which does not know about any scope.
func NewScoped(r Getter, websiteID, storeID int64) Scoped {
	return Scoped{
		Root:      r,
		WebsiteID: websiteID,
		StoreID:   storeID,
	}
}

// IsValid checks if the object has been set up correctly.
func (ss Scoped) IsValid() bool {
	return ss.Root != nil && ((ss.WebsiteID == 0 && ss.StoreID == 0) ||
		(ss.WebsiteID > 0 && ss.StoreID == 0) ||
		(ss.WebsiteID > 0 && ss.StoreID > 0))
}

// ParentID tells you the parent underlying scope and its ID. Store falls back
// to website and website falls back to default.
func (ss Scoped) ParentID() scope.TypeID {
	if ss.StoreID > 0 {
		return scope.Website.Pack(ss.WebsiteID)
	}
	return scope.DefaultTypeID
}

// ScopeID tells you the current underlying scope and its ID to which this
// configuration has been bound to.
func (ss Scoped) ScopeID() scope.TypeID {
	if ss.StoreID > 0 {
		return scope.Store.Pack(ss.StoreID)
	}
	if ss.WebsiteID > 0 {
		return scope.Website.Pack(ss.WebsiteID)
	}
	return scope.DefaultTypeID
}

// ScopeIDs returns the hierarchical order of the scopes containing ScopeID() on
// position zero and ParentID() on position one. This function guarantees that
// the returned slice contains at least two entries.
func (ss Scoped) ScopeIDs() scope.TypeIDs {
	var ids = [2]scope.TypeID{
		ss.ScopeID(),
		ss.ParentID(),
	}
	return ids[:]
}

func (ss Scoped) isAllowedStore(restrictUpTo scope.Type) bool {
	scp := ss.ScopeID().Type()
	if restrictUpTo > scope.Absent {
		scp = restrictUpTo
	}
	return ss.StoreID > 0 && scope.PermStoreReverse.Has(scp)
}

func (ss Scoped) isAllowedWebsite(restrictUpTo scope.Type) bool {
	scp := ss.ScopeID().Type()
	if restrictUpTo > scope.Absent {
		scp = restrictUpTo
	}
	return ss.WebsiteID > 0 && scope.PermWebsiteReverse.Has(scp)
}

// Value traverses through the scopes store->website->default to find a matching
// byte slice value. The argument `restrictUpTo` scope.Type restricts the
// bubbling. For example a path gets stored in all three scopes but argument
// `restrictUpTo` specifies only website scope, then the store scope will be
// ignored for querying. If argument `restrictUpTo` has been set to zero aka.
// scope.Absent, then all three scopes are considered for querying.
func (ss Scoped) Value(restrictUpTo scope.Type, route string) (v Value, ok bool, err error) {
	// fallback to next parent scope if value does not exists
	p := Path{
		route:   route,
		ScopeID: scope.DefaultTypeID,
	}
	if ss.isAllowedStore(restrictUpTo) {
		v, ok, err := ss.Root.Value(p.BindStore(ss.StoreID))
		if ok || err != nil {
			// value found or err is not a NotFound error
			return v, ok, errors.WithStack(err)
		}
	}
	if ss.isAllowedWebsite(restrictUpTo) {
		v, ok, err := ss.Root.Value(p.BindWebsite(ss.WebsiteID))
		if ok || err != nil {
			return v, ok, errors.WithStack(err)
		}
	}
	return ss.Root.Value(p)
}
<|endoftext|>


<commit_before>//go:generate genqrc qml

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/andres-erbsen/chatterbox/client/persistence"
	"github.com/andres-erbsen/chatterbox/proto"
	"gopkg.in/fsnotify.v1"
	"gopkg.in/qml.v1"
)

var root = flag.String("root", "", "chatterbox root directory")

type gui struct {
	persistence.Paths
	engine *qml.Engine

	conversations        []*proto.ConversationMetadata
	conversationsIndex   map[string]int
	conversationsDisplay qml.Object

	watcher *fsnotify.Watcher

	openConversations map[string]*qml.Window

	stop chan struct{}
}

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	flag.Parse()
	if *root == "" {
		fmt.Fprintf(os.Stderr, "USAGE: %s -root=ROOTDIR", os.Args[0])
		os.Exit(1)
	}

	g := &gui{
		stop:               make(chan struct{}),
		conversationsIndex: make(map[string]int),
		openConversations:  make(map[string]*qml.Window),
		Paths: persistence.Paths{
			RootDir:     *root,
			Application: "qmlgui",
		},
	}
	var err error
	g.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	go g.watch()
	if err := qml.Run(g.run); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(2)
	}
}

func toJson(v interface{}) string {
	rawJson, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return string(rawJson)
}

func (g *gui) newConversation() error {
	controls, err := g.engine.LoadFile("qml/new-conversation.qml")
	if err != nil {
		return err
	}
	window := controls.CreateWindow(nil)

	window.ObjectByName("sendMessage").On("triggered", func() {
		to := window.ObjectByName("toField").String("text")
		subject := window.ObjectByName("subjectField").String("text")
		message := window.ObjectByName("messageArea").String("text")

		participants := make([]string, 0, 2)
		for _, dst := range strings.Split(to, ",") {
			participants = append(participants, strings.TrimSpace(dst))
		}
		conv := &proto.ConversationMetadata{
			Participants: participants,
			Subject:      subject,
		}
		if err := g.ConversationToOutbox(conv); err != nil {
			log.Printf("failed to create conversation (maybe already sent?): %s", err)
		}
		if err := g.MessageToOutbox(persistence.ConversationName(conv), message); err != nil {
			log.Printf("failed to send message (maybe already sent?): %s", err)
		}
		window.Call("closeWindow") //overriding native "close" b/c of weird errors
	})

	return nil
}

func (g *gui) handleMessage(path string) {
	convName := filepath.Base(filepath.Dir(path))
	msg, err := persistence.ReadMessageFromFile(path)
	if err != nil {
		log.Printf("error reading message %s: %s\n", path, err)
		return
	}

	qml.Lock()
	defer qml.Unlock()
	win, ok := g.openConversations[convName]
	if !ok {
		log.Printf("ignoring message for %s\nmap: %v", convName, g.openConversations)
		return
	}
	g.displayMessage(win, msg)
}

func (g *gui) displayMessage(window *qml.Window, msg *persistence.Message) {
	window.ObjectByName("messageModel").Call("addItem", toJson(
		&persistence.Message{
			Path:    msg.Path,
			Content: strings.TrimSpace(msg.Content),
			Sender:  msg.Sender,
		}))
	// TODO: only do this if the view was at the end before adding the new item
	window.ObjectByName("messageView").Call("positionViewAtEnd")
}

func (g *gui) openConversation(idx int) error {
	controls, err := g.engine.LoadFile("qml/old-conversation.qml")
	if err != nil {
		return err
	}
	window := controls.CreateWindow(nil)

	conv := g.conversations[idx]

	//TODO: if an open conversation is selected again, focus that window

	qml.Lock()
	g.openConversations[persistence.ConversationName(conv)] = window
	qml.Unlock()

	msgs, err := g.LoadMessages(conv)
	if err != nil {
		panic(err)
	}
	for _, msg := range msgs {
		g.displayMessage(window, msg)
	}
	window.ObjectByName("messageView").Call("positionViewAtEnd")

	window.On("sendMessage", func(message string) {
		err := g.MessageToOutbox(persistence.ConversationName(conv), message)
		if err != nil {
			log.Fatal(err)
		}
	})

	window.On("closing", func() {
		qml.Lock()
		delete(g.openConversations, persistence.ConversationName(conv))
		qml.Unlock()
	})

	return nil
}

func (g *gui) run() error {
	defer close(g.stop)
	g.engine = qml.NewEngine()
	controls, err := g.engine.LoadFile("qml/history.qml")
	if err != nil {
		return err
	}

	window := controls.CreateWindow(nil)
	g.conversationsDisplay = window.ObjectByName("listModel")
	convs, err := g.ListConversations()
	if err != nil {
		return err
	}
	for _, con := range convs {
		g.handleConversation(con)
	}

	window.ObjectByName("table").On("activated", g.openConversation)
	window.ObjectByName("newConversation").On("triggered", g.newConversation)

	window.Show()
	window.Wait()
	return nil
}

func (g *gui) handleConversation(con *proto.ConversationMetadata) {
	if _, already := g.conversationsIndex[persistence.ConversationName(con)]; already {
		return
	}

	err := g.watcher.Add(filepath.Join(g.ConversationDir(), persistence.ConversationName(con)))
	if err != nil {
		log.Printf("error watching conversation %s: %s\n", persistence.ConversationName(con), err)
		// continue after error
	}

	qml.Lock()
	defer qml.Unlock()
	g.conversationsIndex[persistence.ConversationName(con)] = len(g.conversations)
	g.conversations = append(g.conversations, con)
	g.conversationsDisplay.Call("addItem", toJson(con))
}

func (g *gui) watch() {
	defer g.watcher.Close()
	err := g.watcher.Add(g.ConversationDir())
	if err != nil {
		log.Fatal(err)
	}
	for {
		select {
		case <-g.stop:
			return
		case err := <-g.watcher.Errors:
			fmt.Println("error:", err)
		case e := <-g.watcher.Events:
			rpath, err := filepath.Rel(g.ConversationDir(), e.Name)
			if err != nil {
				panic(err)
			}
			if !(e.Op == fsnotify.Create || e.Op == fsnotify.Rename) {
				// TODO: handle move, delete
				continue
			}
			if match, _ := filepath.Match("*", rpath); match {
				// when a conversation is created it MUST have a metadata file when
				// it is moved to the conversations directory
				c, err := persistence.ReadConversationMetadata(e.Name)
				if err != nil {
					log.Printf("error reading metadata of %s: %s\n", rpath, err)
					continue
				}
				g.handleConversation(c)
			} else if match, _ := filepath.Match("*/*", rpath); match {
				g.handleMessage(e.Name)

			} else {
				log.Printf("event at unknown path: %s", rpath)
			}
		}
	}
}
<commit_msg>gui: use embedded resources<commit_after>//go:generate genqrc qml

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/andres-erbsen/chatterbox/client/persistence"
	"github.com/andres-erbsen/chatterbox/proto"
	"gopkg.in/fsnotify.v1"
	"gopkg.in/qml.v1"
)

var root = flag.String("root", "", "chatterbox root directory")

type gui struct {
	persistence.Paths
	engine *qml.Engine

	conversations        []*proto.ConversationMetadata
	conversationsIndex   map[string]int
	conversationsDisplay qml.Object

	watcher *fsnotify.Watcher

	openConversations map[string]*qml.Window

	stop chan struct{}
}

func main() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	flag.Parse()
	if *root == "" {
		fmt.Fprintf(os.Stderr, "USAGE: %s -root=ROOTDIR", os.Args[0])
		os.Exit(1)
	}

	g := &gui{
		stop:               make(chan struct{}),
		conversationsIndex: make(map[string]int),
		openConversations:  make(map[string]*qml.Window),
		Paths: persistence.Paths{
			RootDir:     *root,
			Application: "qmlgui",
		},
	}
	var err error
	g.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	go g.watch()
	if err := qml.Run(g.run); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(2)
	}
}

func toJson(v interface{}) string {
	rawJson, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return string(rawJson)
}

func (g *gui) newConversation() error {
	controls, err := g.engine.LoadFile("qrc:///qml/new-conversation.qml")
	if err != nil {
		return err
	}
	window := controls.CreateWindow(nil)

	window.ObjectByName("sendMessage").On("triggered", func() {
		to := window.ObjectByName("toField").String("text")
		subject := window.ObjectByName("subjectField").String("text")
		message := window.ObjectByName("messageArea").String("text")

		participants := make([]string, 0, 2)
		for _, dst := range strings.Split(to, ",") {
			participants = append(participants, strings.TrimSpace(dst))
		}
		conv := &proto.ConversationMetadata{
			Participants: participants,
			Subject:      subject,
		}
		if err := g.ConversationToOutbox(conv); err != nil {
			log.Printf("failed to create conversation (maybe already sent?): %s", err)
		}
		if err := g.MessageToOutbox(persistence.ConversationName(conv), message); err != nil {
			log.Printf("failed to send message (maybe already sent?): %s", err)
		}
		window.Call("closeWindow") //overriding native "close" b/c of weird errors
	})

	return nil
}

func (g *gui) handleMessage(path string) {
	convName := filepath.Base(filepath.Dir(path))
	msg, err := persistence.ReadMessageFromFile(path)
	if err != nil {
		log.Printf("error reading message %s: %s\n", path, err)
		return
	}

	qml.Lock()
	defer qml.Unlock()
	win, ok := g.openConversations[convName]
	if !ok {
		log.Printf("ignoring message for %s\nmap: %v", convName, g.openConversations)
		return
	}
	g.displayMessage(win, msg)
}

func (g *gui) displayMessage(window *qml.Window, msg *persistence.Message) {
	window.ObjectByName("messageModel").Call("addItem", toJson(
		&persistence.Message{
			Path:    msg.Path,
			Content: strings.TrimSpace(msg.Content),
			Sender:  msg.Sender,
		}))
	// TODO: only do this if the view was at the end before adding the new item
	window.ObjectByName("messageView").Call("positionViewAtEnd")
}

func (g *gui) openConversation(idx int) error {
	controls, err := g.engine.LoadFile("qrc:///qml/old-conversation.qml")
	if err != nil {
		return err
	}
	window := controls.CreateWindow(nil)

	conv := g.conversations[idx]

	//TODO: if an open conversation is selected again, focus that window

	qml.Lock()
	g.openConversations[persistence.ConversationName(conv)] = window
	qml.Unlock()

	msgs, err := g.LoadMessages(conv)
	if err != nil {
		panic(err)
	}
	for _, msg := range msgs {
		g.displayMessage(window, msg)
	}
	window.ObjectByName("messageView").Call("positionViewAtEnd")

	window.On("sendMessage", func(message string) {
		err := g.MessageToOutbox(persistence.ConversationName(conv), message)
		if err != nil {
			log.Fatal(err)
		}
	})

	window.On("closing", func() {
		qml.Lock()
		delete(g.openConversations, persistence.ConversationName(conv))
		qml.Unlock()
	})

	return nil
}

func (g *gui) run() error {
	defer close(g.stop)
	g.engine = qml.NewEngine()
	controls, err := g.engine.LoadFile("qrc:///qml/history.qml")
	if err != nil {
		return err
	}

	window := controls.CreateWindow(nil)
	g.conversationsDisplay = window.ObjectByName("listModel")
	convs, err := g.ListConversations()
	if err != nil {
		return err
	}
	for _, con := range convs {
		g.handleConversation(con)
	}

	window.ObjectByName("table").On("activated", g.openConversation)
	window.ObjectByName("newConversation").On("triggered", g.newConversation)

	window.Show()
	window.Wait()
	return nil
}

func (g *gui) handleConversation(con *proto.ConversationMetadata) {
	if _, already := g.conversationsIndex[persistence.ConversationName(con)]; already {
		return
	}

	err := g.watcher.Add(filepath.Join(g.ConversationDir(), persistence.ConversationName(con)))
	if err != nil {
		log.Printf("error watching conversation %s: %s\n", persistence.ConversationName(con), err)
		// continue after error
	}

	qml.Lock()
	defer qml.Unlock()
	g.conversationsIndex[persistence.ConversationName(con)] = len(g.conversations)
	g.conversations = append(g.conversations, con)
	g.conversationsDisplay.Call("addItem", toJson(con))
}

func (g *gui) watch() {
	defer g.watcher.Close()
	err := g.watcher.Add(g.ConversationDir())
	if err != nil {
		log.Fatal(err)
	}
	for {
		select {
		case <-g.stop:
			return
		case err := <-g.watcher.Errors:
			fmt.Println("error:", err)
		case e := <-g.watcher.Events:
			rpath, err := filepath.Rel(g.ConversationDir(), e.Name)
			if err != nil {
				panic(err)
			}
			if !(e.Op == fsnotify.Create || e.Op == fsnotify.Rename) {
				// TODO: handle move, delete
				continue
			}
			if match, _ := filepath.Match("*", rpath); match {
				// when a conversation is created it MUST have a metadata file when
				// it is moved to the conversations directory
				c, err := persistence.ReadConversationMetadata(e.Name)
				if err != nil {
					log.Printf("error reading metadata of %s: %s\n", rpath, err)
					continue
				}
				g.handleConversation(c)
			} else if match, _ := filepath.Match("*/*", rpath); match {
				g.handleMessage(e.Name)

			} else {
				log.Printf("event at unknown path: %s", rpath)
			}
		}
	}
}
<|endoftext|>


<commit_before>package rest

import (
	"github.com/synapse-garden/sg-proto/admin"
	"github.com/synapse-garden/sg-proto/auth"
	"github.com/synapse-garden/sg-proto/incept"
	"github.com/synapse-garden/sg-proto/store"
	"github.com/synapse-garden/sg-proto/stream"
	"github.com/synapse-garden/sg-proto/stream/river"
	"github.com/synapse-garden/sg-proto/users"

	"github.com/boltdb/bolt"
	"github.com/julienschmidt/httprouter"
)

// Needed endpoints:
//
// Admin:
//  - Admin auth middleware
//  - POST /admin/tickets (optionally ?count=n)
//  - GET /admin/tickets
//  - DELETE /admin/tickets/:credential
//
// Create a new user:
//  - POST /incept/:credential ("magic link")
//
// Get a new session / login:
//  - POST /session/:user_id :pwhash (returns Token to be included as Authorization: Bearer)
//
// User account
//  - GET  /profile (user ID inferred) => /users/:id
//  - PUT  /profile (nothing to update yet)
//  - DELETE /profile => delete user account and any logins
//
// Open a new chat socket
//  - GET /chat/:user_id
//
// TODOs / tasks
//  - POST /todo {bounty, due}
//  - POST /todo/:id/complete => Get bounty if before due

// API is a transform on an httprouter.Router, passing a DB for passing
// on to httprouter.Handles.
type API func(*httprouter.Router, *bolt.DB) error

// Bind binds the API on the given DB.  It sets up REST endpoints as needed.
func Bind(
	db *bolt.DB,
	source *SourceInfo,
	apiKey auth.Token,
) (*httprouter.Router, error) {
	if err := db.Update(store.Prep(
		admin.AdminBucket,
		incept.TicketBucket,
		users.UserBucket,
		auth.LoginBucket,
		auth.SessionBucket,
		auth.RefreshBucket,
		auth.ContextBucket,
		stream.StreamBucket,
		river.RiverBucket,
	)); err != nil {
		return nil, err
	}
	htr := httprouter.New()
	for _, api := range []API{
		Source(source),
		Admin(apiKey),
		Incept,
		Token,
		Profile,
		Stream,
	} {
		if err := api(htr, db); err != nil {
			return nil, err
		}
	}

	return htr, nil
}
<commit_msg>Add REST endpoint and buckets for convos<commit_after>package rest

import (
	"github.com/synapse-garden/sg-proto/admin"
	"github.com/synapse-garden/sg-proto/auth"
	"github.com/synapse-garden/sg-proto/convo"
	"github.com/synapse-garden/sg-proto/incept"
	"github.com/synapse-garden/sg-proto/store"
	"github.com/synapse-garden/sg-proto/stream"
	"github.com/synapse-garden/sg-proto/stream/river"
	"github.com/synapse-garden/sg-proto/users"

	"github.com/boltdb/bolt"
	"github.com/julienschmidt/httprouter"
)

// Needed endpoints:
//
// Admin:
//  - Admin auth middleware
//  - POST /admin/tickets (optionally ?count=n)
//  - GET /admin/tickets
//  - DELETE /admin/tickets/:credential
//
// Create a new user:
//  - POST /incept/:credential ("magic link")
//
// Get a new session / login:
//  - POST /session/:user_id :pwhash (returns Token to be included as Authorization: Bearer)
//
// User account
//  - GET  /profile (user ID inferred) => /users/:id
//  - PUT  /profile (nothing to update yet)
//  - DELETE /profile => delete user account and any logins
//
// Open a new chat socket
//  - GET /chat/:user_id
//
// TODOs / tasks
//  - POST /todo {bounty, due}
//  - POST /todo/:id/complete => Get bounty if before due

// API is a transform on an httprouter.Router, passing a DB for passing
// on to httprouter.Handles.
type API func(*httprouter.Router, *bolt.DB) error

// Bind binds the API on the given DB.  It sets up REST endpoints as needed.
func Bind(
	db *bolt.DB,
	source *SourceInfo,
	apiKey auth.Token,
) (*httprouter.Router, error) {
	if err := db.Update(store.Prep(
		admin.AdminBucket,
		incept.TicketBucket,
		users.UserBucket,
		auth.LoginBucket,
		auth.SessionBucket,
		auth.RefreshBucket,
		auth.ContextBucket,
		stream.StreamBucket,
		river.RiverBucket,
		convo.ConvoBucket,
		convo.MessageBucket,
	)); err != nil {
		return nil, err
	}

	htr := httprouter.New()
	for _, api := range []API{
		Source(source),
		Admin(apiKey),
		Incept,
		Token,
		Profile,
		Stream,
		Convo,
	} {
		if err := api(htr, db); err != nil {
			return nil, err
		}
	}

	return htr, nil
}
<|endoftext|>


<commit_before>// Package checkers provides utilities for implementing lint checkers.
package checkers

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

	"sync"

	"os"
)

// Error returns an error containing errs.
//
// If errs is empty, nil is returned. If not the returned
// error will implement the following interface
//
//     type errors interface {
//     	Errors() []string
//     }
//
// and return errs unmodified.
func Error(errs ...string) error {
	if len(errs) == 0 {
		return nil
	}
	return errorList(errs)
}

type errorList []string

func (e errorList) Errors() []string { return []string(e) }
func (e errorList) Error() string    { return strings.Join(e, "\n") }

func packageDir(path string) (string, error) {
	pkg, err := build.Import(path, ".", build.FindOnly)
	if err != nil {
		return "", fmt.Errorf("import failed: %s: %v", path, err)
	}
	return pkg.Dir, nil
}

// FindBin returns bin if it exists in the path. If not it checks
// go bin directories ($GOROOT/bin and $GOPATH/bin) and returns that if it exists.
// If neither exist it returns an error.
func FindBin(bin string) (string, error) {
	if _, err := exec.LookPath(bin); err == nil {
		return bin, nil
	}
	srcDirs := build.Default.SrcDirs()
	for _, src := range srcDirs {
		binFile := filepath.Join(filepath.Dir(src), "bin", bin)
		if _, err := exec.LookPath(binFile); err == nil {
			return binFile, nil
		}
	}
	return "", fmt.Errorf("failed to find binary: %v", bin)
}

// InstallMissing runs go get importPath if bin cannot be found in the directories
// contained in the PATH environment variable. It returns the path to the installed
// binary on success.
func InstallMissing(bin, importPath string) (string, error) {
	if b, err := FindBin(bin); err == nil {
		return b, nil
	}
	if data, err := exec.Command("go", "get", importPath).CombinedOutput(); err != nil {
		return "", fmt.Errorf("failed to get %s: %v: %s", importPath, err, string(data))
	}

	if data, err := exec.Command("go", "install", importPath).CombinedOutput(); err != nil {
		return "", fmt.Errorf("failed to install %s: %v: %s", importPath, err, string(data))
	}
	b, err := FindBin(bin)
	if err != nil {
		return "", fmt.Errorf("failed to lookup %v after install: %v", bin, err)
	}
	return b, nil
}

// Lint runs the linter specified by bin for each package in pkgs.
// The linter is installed if necessary using go get importPath.
func Lint(bin, importPath string, pkgs []string) error {
	b, err := InstallMissing(bin, importPath)
	if err != nil {
		return err
	}
	var errs []string
	for _, pkg := range pkgs {
		p, perr := Load(pkg)
		if perr != nil {
			return fmt.Errorf("failed to load pkg info: %s: %v", pkg, perr)
		}
		result, _ := Exec(exec.Command(b, p.Path))
		str := strings.TrimSpace(
			strings.TrimSpace(result.Stdout) + "\n" + strings.TrimSpace(result.Stderr))
		if str == "" {
			continue
		}
		errs = append(errs, strings.Split(str, "\n")...)
	}
	return Error(errs...)
}

// ExecResult holds a status code, stdout and stderr for a single command execution.
type ExecResult struct {
	Code   int
	Stdout string
	Stderr string
}

// Exec executes cmd and results exit code, stdout and stderr of the result. It
// additionally returns and error if the status code is not 0.
func Exec(cmd *exec.Cmd) (ExecResult, error) {
	res := ExecResult{Code: -1}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return res, fmt.Errorf("cmd.StdoutPipe failed: %v", err)
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return res, fmt.Errorf("cmd.StderrPipe failed: %v", err)
	}
	if err = cmd.Start(); err != nil {
		return res, fmt.Errorf("cmd.Start failed: %v", err)
	}
	data, err := ioutil.ReadAll(stdout)
	if err != nil {
		return res, fmt.Errorf("failed to read stdout: %v", err)
	}
	res.Stdout = string(data)
	if data, err = ioutil.ReadAll(stderr); err != nil {
		return res, fmt.Errorf("failed to read stderr: %v", err)
	}
	res.Stderr = string(data)
	err = cmd.Wait()
	if st, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
		res.Code = st.ExitStatus()
	}
	return res, err
}

// GoFiles lists all .go files in pkgs.
func GoFiles(pkgs ...string) ([]string, error) {
	var files []string
	for _, pkg := range pkgs {
		p, err := Load(pkg)
		if err != nil {
			return nil, fmt.Errorf("failed to load go files for %s: %v", pkg, err)
		}
		files = append(files, p.GoFiles...)
	}
	return files, nil
}

func filterGoFiles(files []string) []string {
	gofiles, i := make([]string, len(files)), 0
	for _, f := range files {
		if !strings.HasSuffix(f, ".go") || strings.HasSuffix(f, "_test.go") {
			continue
		}
		gofiles[i] = f
		i++
	}
	return gofiles[:i]
}

// Package holds file and sub directory information about a single package.
type Package struct {
	// The path of the package. Can be a wildcard path such as ...
	Path string
	// Files holds all files in the package. If the Path is a wildcard path
	// (...) files in sub packages are also returned.
	Files []string
	// All files in Files with a .go extension
	GoFiles []string
	// All sub packages if Path is a wildcard, or just Path if not.
	Pkgs []string
}

var (
	packages     = map[string]*Package{}
	packageMutex sync.Mutex
)

// Unload removes any information about pkg from the cache.
func Unload(pkg string) {
	packageMutex.Lock()
	defer packageMutex.Unlock()
	delete(packages, pkg)
}

// Load returns a cached Package instance if one exists or creates a new instance if not.
// It returns an error if there was an error reading package information.
func Load(pkg string) (*Package, error) {
	packageMutex.Lock()
	defer packageMutex.Unlock()
	p := packages[pkg]
	if p != nil {
		return p, nil
	}
	p = &Package{Path: pkg}
	if err := p.load(); err != nil {
		return nil, err
	}
	packages[pkg] = p
	return p, nil
}

func (p *Package) load() error {
	if err := p.readPackages(); err != nil {
		return fmt.Errorf("failed read sub packages: %s: %v", p.Path, err)
	}
	if err := p.readFiles(); err != nil {
		return fmt.Errorf("failed to list files: %s: %v", p.Path, err)
	}
	p.GoFiles = filterGoFiles(p.Files)
	return nil
}

func (p *Package) readFiles() error {
	var res []string
	for _, pkg := range p.Pkgs {
		dir, err := packageDir(pkg)
		if err != nil {
			return err
		}
		entries, err := ioutil.ReadDir(dir)
		if err != nil {
			return fmt.Errorf("failed to list dir %s: %v", dir, err)
		}
		files, i := make([]string, len(entries)), 0
		for _, entry := range entries {
			if entry.IsDir() {
				continue
			}
			files[i] = filepath.Join(dir, entry.Name())
			i++
		}
		res = append(res, files[:i]...)
	}
	p.Files = res
	return nil
}

func (p *Package) readPackages() error {
	wd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to find cwd: %v", err)
	}
	if filepath.Base(p.Path) != "..." {
		b, err := build.Import(p.Path, wd, build.FindOnly)
		if err != nil {
			return fmt.Errorf("import failed: %s: %v", p.Path, err)
		}
		p.Pkgs = []string{b.ImportPath}
		return nil
	}

	d := filepath.Dir(p.Path)
	b, err := build.Import(d, wd, build.FindOnly)
	if err != nil {
		return fmt.Errorf("import failed %s: %v", d, err)
	}
	dir := b.Dir
	var paths []string
	err = filepath.Walk(dir, func(path string, stat os.FileInfo, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if !stat.IsDir() {
			return nil
		}
		p, perr := build.ImportDir(path, build.FindOnly)
		if perr != nil {
			if _, noGo := perr.(*build.NoGoError); noGo {
				return nil
			}
			return fmt.Errorf("import failed: %s: %v", path, perr)
		}
		paths = append(paths, p.ImportPath)
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to list %s: %v", dir, err)
	}
	p.Pkgs = paths
	return nil
}
<commit_msg>Add args to checkers.Lint<commit_after>// Package checkers provides utilities for implementing lint checkers.
package checkers

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

	"sync"

	"os"
)

// Error returns an error containing errs.
//
// If errs is empty, nil is returned. If not the returned
// error will implement the following interface
//
//     type errors interface {
//     	Errors() []string
//     }
//
// and return errs unmodified.
func Error(errs ...string) error {
	if len(errs) == 0 {
		return nil
	}
	return errorList(errs)
}

type errorList []string

func (e errorList) Errors() []string { return []string(e) }
func (e errorList) Error() string    { return strings.Join(e, "\n") }

func packageDir(path string) (string, error) {
	pkg, err := build.Import(path, ".", build.FindOnly)
	if err != nil {
		return "", fmt.Errorf("import failed: %s: %v", path, err)
	}
	return pkg.Dir, nil
}

// FindBin returns bin if it exists in the path. If not it checks
// go bin directories ($GOROOT/bin and $GOPATH/bin) and returns that if it exists.
// If neither exist it returns an error.
func FindBin(bin string) (string, error) {
	if _, err := exec.LookPath(bin); err == nil {
		return bin, nil
	}
	srcDirs := build.Default.SrcDirs()
	for _, src := range srcDirs {
		binFile := filepath.Join(filepath.Dir(src), "bin", bin)
		if _, err := exec.LookPath(binFile); err == nil {
			return binFile, nil
		}
	}
	return "", fmt.Errorf("failed to find binary: %v", bin)
}

// InstallMissing runs go get importPath if bin cannot be found in the directories
// contained in the PATH environment variable. It returns the path to the installed
// binary on success.
func InstallMissing(bin, importPath string) (string, error) {
	if b, err := FindBin(bin); err == nil {
		return b, nil
	}
	if data, err := exec.Command("go", "get", importPath).CombinedOutput(); err != nil {
		return "", fmt.Errorf("failed to get %s: %v: %s", importPath, err, string(data))
	}

	if data, err := exec.Command("go", "install", importPath).CombinedOutput(); err != nil {
		return "", fmt.Errorf("failed to install %s: %v: %s", importPath, err, string(data))
	}
	b, err := FindBin(bin)
	if err != nil {
		return "", fmt.Errorf("failed to lookup %v after install: %v", bin, err)
	}
	return b, nil
}

// Lint runs the linter specified by bin for each package in pkgs.
// The linter is installed if necessary using go get importPath.
func Lint(bin, importPath string, pkgs []string, args ...string) error {
	b, err := InstallMissing(bin, importPath)
	if err != nil {
		return err
	}
	var errs []string
	for _, pkg := range pkgs {
		p, perr := Load(pkg)
		if perr != nil {
			return fmt.Errorf("failed to load pkg info: %s: %v", pkg, perr)
		}
		result, _ := Exec(exec.Command(b, append(args, p.Path)...))
		str := strings.TrimSpace(
			strings.TrimSpace(result.Stdout) + "\n" + strings.TrimSpace(result.Stderr))
		if str == "" {
			continue
		}
		errs = append(errs, strings.Split(str, "\n")...)
	}
	return Error(errs...)
}

// ExecResult holds a status code, stdout and stderr for a single command execution.
type ExecResult struct {
	Code   int
	Stdout string
	Stderr string
}

// Exec executes cmd and results exit code, stdout and stderr of the result. It
// additionally returns and error if the status code is not 0.
func Exec(cmd *exec.Cmd) (ExecResult, error) {
	res := ExecResult{Code: -1}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return res, fmt.Errorf("cmd.StdoutPipe failed: %v", err)
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return res, fmt.Errorf("cmd.StderrPipe failed: %v", err)
	}
	if err = cmd.Start(); err != nil {
		return res, fmt.Errorf("cmd.Start failed: %v", err)
	}
	data, err := ioutil.ReadAll(stdout)
	if err != nil {
		return res, fmt.Errorf("failed to read stdout: %v", err)
	}
	res.Stdout = string(data)
	if data, err = ioutil.ReadAll(stderr); err != nil {
		return res, fmt.Errorf("failed to read stderr: %v", err)
	}
	res.Stderr = string(data)
	err = cmd.Wait()
	if st, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
		res.Code = st.ExitStatus()
	}
	return res, err
}

// GoFiles lists all .go files in pkgs.
func GoFiles(pkgs ...string) ([]string, error) {
	var files []string
	for _, pkg := range pkgs {
		p, err := Load(pkg)
		if err != nil {
			return nil, fmt.Errorf("failed to load go files for %s: %v", pkg, err)
		}
		files = append(files, p.GoFiles...)
	}
	return files, nil
}

func filterGoFiles(files []string) []string {
	gofiles, i := make([]string, len(files)), 0
	for _, f := range files {
		if !strings.HasSuffix(f, ".go") || strings.HasSuffix(f, "_test.go") {
			continue
		}
		gofiles[i] = f
		i++
	}
	return gofiles[:i]
}

// Package holds file and sub directory information about a single package.
type Package struct {
	// The path of the package. Can be a wildcard path such as ...
	Path string
	// Files holds all files in the package. If the Path is a wildcard path
	// (...) files in sub packages are also returned.
	Files []string
	// All files in Files with a .go extension
	GoFiles []string
	// All sub packages if Path is a wildcard, or just Path if not.
	Pkgs []string
}

var (
	packages     = map[string]*Package{}
	packageMutex sync.Mutex
)

// Unload removes any information about pkg from the cache.
func Unload(pkg string) {
	packageMutex.Lock()
	defer packageMutex.Unlock()
	delete(packages, pkg)
}

// Load returns a cached Package instance if one exists or creates a new instance if not.
// It returns an error if there was an error reading package information.
func Load(pkg string) (*Package, error) {
	packageMutex.Lock()
	defer packageMutex.Unlock()
	p := packages[pkg]
	if p != nil {
		return p, nil
	}
	p = &Package{Path: pkg}
	if err := p.load(); err != nil {
		return nil, err
	}
	packages[pkg] = p
	return p, nil
}

func (p *Package) load() error {
	if err := p.readPackages(); err != nil {
		return fmt.Errorf("failed read sub packages: %s: %v", p.Path, err)
	}
	if err := p.readFiles(); err != nil {
		return fmt.Errorf("failed to list files: %s: %v", p.Path, err)
	}
	p.GoFiles = filterGoFiles(p.Files)
	return nil
}

func (p *Package) readFiles() error {
	var res []string
	for _, pkg := range p.Pkgs {
		dir, err := packageDir(pkg)
		if err != nil {
			return err
		}
		entries, err := ioutil.ReadDir(dir)
		if err != nil {
			return fmt.Errorf("failed to list dir %s: %v", dir, err)
		}
		files, i := make([]string, len(entries)), 0
		for _, entry := range entries {
			if entry.IsDir() {
				continue
			}
			files[i] = filepath.Join(dir, entry.Name())
			i++
		}
		res = append(res, files[:i]...)
	}
	p.Files = res
	return nil
}

func (p *Package) readPackages() error {
	wd, err := os.Getwd()
	if err != nil {
		return fmt.Errorf("failed to find cwd: %v", err)
	}
	if filepath.Base(p.Path) != "..." {
		b, err := build.Import(p.Path, wd, build.FindOnly)
		if err != nil {
			return fmt.Errorf("import failed: %s: %v", p.Path, err)
		}
		p.Pkgs = []string{b.ImportPath}
		return nil
	}

	d := filepath.Dir(p.Path)
	b, err := build.Import(d, wd, build.FindOnly)
	if err != nil {
		return fmt.Errorf("import failed %s: %v", d, err)
	}
	dir := b.Dir
	var paths []string
	err = filepath.Walk(dir, func(path string, stat os.FileInfo, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if !stat.IsDir() {
			return nil
		}
		p, perr := build.ImportDir(path, build.FindOnly)
		if perr != nil {
			if _, noGo := perr.(*build.NoGoError); noGo {
				return nil
			}
			return fmt.Errorf("import failed: %s: %v", path, perr)
		}
		paths = append(paths, p.ImportPath)
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to list %s: %v", dir, err)
	}
	p.Pkgs = paths
	return nil
}
<|endoftext|>