<commit_before>package golatch

import (
	"encoding/json"
	"strings"
)

type LatchResponse interface {
	Unmarshal(Json string) error
}

type LatchErrorResponse struct {
	Err LatchError `json:"error"`
}

type LatchPairResponse struct {
	Data struct {
		AccountId string `json:"accountId"`
	} `json:"data"`
}

type LatchStatusResponse struct {
	Data struct {
		Operations map[string]LatchOperationStatus `json:"operations"`
	} `json:"data"`
}

type LatchOperationStatus struct {
	Status     string                          `json:"status"`
	TwoFactor  LatchTwoFactor                  `json:"two_factor"`
	Operations map[string]LatchOperationStatus `json:"operations"`
}

type LatchTwoFactor struct {
	Token     string `json:"token"`
	Generated uint64 `json:"generated"`
}

type LatchAddOperationResponse struct {
	Data struct {
		OperationId string `json:"operationId"`
	} `json:"data"`
}

type LatchShowOperationResponse struct {
	Data struct {
		Operations map[string]LatchOperation `json:"operations"`
	} `json:"data"`
}

type LatchOperation struct {
	Name          string                    `json:"name"`
	Status        string                    `json:"status"`
	TwoFactor     string                    `json:"two_factor"`
	LockOnRequest string                    `json:"lock_on_request"`
	Operations    map[string]LatchOperation `json:"operations"`
}

type LatchHistoryResponse struct {
	AppID string
	Data  struct {
		Application   LatchApplication     `json:"application"`
		LastSeen      uint64               `json:"lastSeen"`
		ClientVersion []LatchClientVersion `json:"clientVersion"`
		HistoryCount  int                  `json:"count"`
		History       []LatchHistoryEntry  `json:"history"`
	} `json:"data"`
}

type LatchApplication struct {
	Status   string `json:"status"`
	PairedOn uint64 `json:"pairedOn"`
	LatchApplicationInfo
}

type LatchClientVersion struct {
	Platform string `json:platform`
	App      string `json:app`
}

type LatchHistoryEntry struct {
	Time      uint64 `json:"t"`
	Action    string `json:"action"`
	What      string `json:"what"`
	Value     string `json:"value"`
	Was       string `json:"was"`
	Name      string `json:"name"`
	UserAgent string `json:"userAgent"`
	IP        string `json:"ip"`
}

type LatchShowApplicationsResponse struct {
	Data struct {
		Applications map[string]LatchApplicationInfo `json:"operations"`
	} `json:"data"`
}

type LatchApplicationInfo struct {
	Name          string                    `json:"name"`
	Description   string                    `json:"description"`
	Secret        string                    `json:"secret"`
	ImageURL      string                    `json:"imageURL"`
	ContactPhone  string                    `json:"contactPhone"`
	ContactEmail  string                    `json:"contactEmail"`
	TwoFactor     string                    `json:"two_factor"`
	LockOnRequest string                    `json:"lock_on_request"`
	Operations    map[string]LatchOperation `json:"operations"`
}

type LatchAddApplicationResponse struct {
	Data struct {
		AppID  string `json:"applicationId"`
		Secret string `json:"secret"`
	} `json:"data"`
}

type LatchSubscriptionResponse struct {
	Data struct {
		Subscription struct {
			ID           string                            `json:"id"`
			Applications LatchSubscriptionUsage            `json:"applications"`
			Operations   map[string]LatchSubscriptionUsage `json:"operations"`
			Users        LatchSubscriptionUsage            `json:"users"`
		} `json:"subscription"`
	} `json:"data"`
}

type LatchSubscriptionUsage struct {
	InUse int `json:"inUse"`
	Limit int `json:"limit"`
}

func (l *LatchErrorResponse) Unmarshal(Json string) error {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchPairResponse) Unmarshal(Json string) error {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchStatusResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchAddOperationResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchShowOperationResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchHistoryResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(strings.Replace(Json, l.AppID, "application", 1)), l)
}

func (l *LatchPairResponse) AccountId() string {
	return l.Data.AccountId
}

func (l *LatchAddOperationResponse) OperationId() string {
	return l.Data.OperationId
}

func (l *LatchStatusResponse) GetParentOperation() (operation LatchOperationStatus) {
	for _, operation = range l.Data.Operations {
		break
	}
	return
}

func (l *LatchStatusResponse) Status() string {
	return l.GetParentOperation().Status
}

func (l *LatchStatusResponse) TwoFactor() LatchTwoFactor {
	return l.GetParentOperation().TwoFactor
}

func (l *LatchStatusResponse) Operations() map[string]LatchOperationStatus {
	return l.GetParentOperation().Operations
}

func (l *LatchShowOperationResponse) Operations() (operations map[string]LatchOperation) {
	return l.Data.Operations
}

func (l *LatchShowOperationResponse) FirstOperation() (operationId string, operation LatchOperation) {
	for operationId, operation = range l.Data.Operations {
		break
	}
	return
}

func (l *LatchHistoryResponse) Application() LatchApplication {
	return l.Data.Application
}

func (l *LatchHistoryResponse) LastSeen() uint64 {
	return l.Data.LastSeen
}

func (l *LatchHistoryResponse) ClientVersion() []LatchClientVersion {
	return l.Data.ClientVersion
}

func (l *LatchHistoryResponse) HistoryCount() int {
	return l.Data.HistoryCount
}

func (l *LatchHistoryResponse) History() []LatchHistoryEntry {
	return l.Data.History
}

func (l *LatchShowApplicationsResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchShowApplicationsResponse) Applications() map[string]LatchApplicationInfo {
	return l.Data.Applications
}

func (l *LatchAddApplicationResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchAddApplicationResponse) AppID() string {
	return l.Data.AppID
}

func (l *LatchAddApplicationResponse) Secret() string {
	return l.Data.Secret
}

func (l *LatchSubscriptionResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchSubscriptionResponse) ID() string {
	return l.Data.Subscription.ID
}

func (l *LatchSubscriptionResponse) Applications() LatchSubscriptionUsage {
	return l.Data.Subscription.Applications
}

func (l *LatchSubscriptionResponse) Operations() map[string]LatchSubscriptionUsage {
	return l.Data.Subscription.Operations
}

func (l *LatchSubscriptionResponse) Users() LatchSubscriptionUsage {
	return l.Data.Subscription.Users
}
<commit_msg>Modified timestamps to be of type int64 instead of uint64.<commit_after>package golatch

import (
	"encoding/json"
	"strings"
)

type LatchResponse interface {
	Unmarshal(Json string) error
}

type LatchErrorResponse struct {
	Err LatchError `json:"error"`
}

type LatchPairResponse struct {
	Data struct {
		AccountId string `json:"accountId"`
	} `json:"data"`
}

type LatchStatusResponse struct {
	Data struct {
		Operations map[string]LatchOperationStatus `json:"operations"`
	} `json:"data"`
}

type LatchOperationStatus struct {
	Status     string                          `json:"status"`
	TwoFactor  LatchTwoFactor                  `json:"two_factor"`
	Operations map[string]LatchOperationStatus `json:"operations"`
}

type LatchTwoFactor struct {
	Token     string `json:"token"`
	Generated int64  `json:"generated"`
}

type LatchAddOperationResponse struct {
	Data struct {
		OperationId string `json:"operationId"`
	} `json:"data"`
}

type LatchShowOperationResponse struct {
	Data struct {
		Operations map[string]LatchOperation `json:"operations"`
	} `json:"data"`
}

type LatchOperation struct {
	Name          string                    `json:"name"`
	Status        string                    `json:"status"`
	TwoFactor     string                    `json:"two_factor"`
	LockOnRequest string                    `json:"lock_on_request"`
	Operations    map[string]LatchOperation `json:"operations"`
}

type LatchHistoryResponse struct {
	AppID string
	Data  struct {
		Application   LatchApplication     `json:"application"`
		LastSeen      int64                `json:"lastSeen"`
		ClientVersion []LatchClientVersion `json:"clientVersion"`
		HistoryCount  int                  `json:"count"`
		History       []LatchHistoryEntry  `json:"history"`
	} `json:"data"`
}

type LatchApplication struct {
	Status   string `json:"status"`
	PairedOn int64  `json:"pairedOn"`
	LatchApplicationInfo
}

type LatchClientVersion struct {
	Platform string `json:platform`
	App      string `json:app`
}

type LatchHistoryEntry struct {
	Time      int64  `json:"t"`
	Action    string `json:"action"`
	What      string `json:"what"`
	Value     string `json:"value"`
	Was       string `json:"was"`
	Name      string `json:"name"`
	UserAgent string `json:"userAgent"`
	IP        string `json:"ip"`
}

type LatchShowApplicationsResponse struct {
	Data struct {
		Applications map[string]LatchApplicationInfo `json:"operations"`
	} `json:"data"`
}

type LatchApplicationInfo struct {
	Name          string                    `json:"name"`
	Description   string                    `json:"description"`
	Secret        string                    `json:"secret"`
	ImageURL      string                    `json:"imageURL"`
	ContactPhone  string                    `json:"contactPhone"`
	ContactEmail  string                    `json:"contactEmail"`
	TwoFactor     string                    `json:"two_factor"`
	LockOnRequest string                    `json:"lock_on_request"`
	Operations    map[string]LatchOperation `json:"operations"`
}

type LatchAddApplicationResponse struct {
	Data struct {
		AppID  string `json:"applicationId"`
		Secret string `json:"secret"`
	} `json:"data"`
}

type LatchSubscriptionResponse struct {
	Data struct {
		Subscription struct {
			ID           string                            `json:"id"`
			Applications LatchSubscriptionUsage            `json:"applications"`
			Operations   map[string]LatchSubscriptionUsage `json:"operations"`
			Users        LatchSubscriptionUsage            `json:"users"`
		} `json:"subscription"`
	} `json:"data"`
}

type LatchSubscriptionUsage struct {
	InUse int `json:"inUse"`
	Limit int `json:"limit"`
}

func (l *LatchErrorResponse) Unmarshal(Json string) error {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchPairResponse) Unmarshal(Json string) error {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchStatusResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchAddOperationResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchShowOperationResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchHistoryResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(strings.Replace(Json, l.AppID, "application", 1)), l)
}

func (l *LatchPairResponse) AccountId() string {
	return l.Data.AccountId
}

func (l *LatchAddOperationResponse) OperationId() string {
	return l.Data.OperationId
}

func (l *LatchStatusResponse) GetParentOperation() (operation LatchOperationStatus) {
	for _, operation = range l.Data.Operations {
		break
	}
	return
}

func (l *LatchStatusResponse) Status() string {
	return l.GetParentOperation().Status
}

func (l *LatchStatusResponse) TwoFactor() LatchTwoFactor {
	return l.GetParentOperation().TwoFactor
}

func (l *LatchStatusResponse) Operations() map[string]LatchOperationStatus {
	return l.GetParentOperation().Operations
}

func (l *LatchShowOperationResponse) Operations() (operations map[string]LatchOperation) {
	return l.Data.Operations
}

func (l *LatchShowOperationResponse) FirstOperation() (operationId string, operation LatchOperation) {
	for operationId, operation = range l.Data.Operations {
		break
	}
	return
}

func (l *LatchHistoryResponse) Application() LatchApplication {
	return l.Data.Application
}

func (l *LatchHistoryResponse) LastSeen() int64 {
	return l.Data.LastSeen
}

func (l *LatchHistoryResponse) ClientVersion() []LatchClientVersion {
	return l.Data.ClientVersion
}

func (l *LatchHistoryResponse) HistoryCount() int {
	return l.Data.HistoryCount
}

func (l *LatchHistoryResponse) History() []LatchHistoryEntry {
	return l.Data.History
}

func (l *LatchShowApplicationsResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchShowApplicationsResponse) Applications() map[string]LatchApplicationInfo {
	return l.Data.Applications
}

func (l *LatchAddApplicationResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchAddApplicationResponse) AppID() string {
	return l.Data.AppID
}

func (l *LatchAddApplicationResponse) Secret() string {
	return l.Data.Secret
}

func (l *LatchSubscriptionResponse) Unmarshal(Json string) (err error) {
	return json.Unmarshal([]byte(Json), l)
}

func (l *LatchSubscriptionResponse) ID() string {
	return l.Data.Subscription.ID
}

func (l *LatchSubscriptionResponse) Applications() LatchSubscriptionUsage {
	return l.Data.Subscription.Applications
}

func (l *LatchSubscriptionResponse) Operations() map[string]LatchSubscriptionUsage {
	return l.Data.Subscription.Operations
}

func (l *LatchSubscriptionResponse) Users() LatchSubscriptionUsage {
	return l.Data.Subscription.Users
}
<|endoftext|>


<commit_before>// Copyright 2015 The rkt Authors
//
// 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 main

import (
	"fmt"
	"strings"

	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/dustin/go-humanize"
	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
	"github.com/coreos/rkt/store"
)

const (
	defaultTimeLayout = "2006-01-02 15:04:05.999 -0700 MST"

	keyField        = "key"
	nameField       = "name"
	importTimeField = "importtime"
	latestField     = "latest"
)

var (
	// map of valid fields and related flag value
	imagesAllFields = map[string]struct{}{
		keyField:        struct{}{},
		nameField:       struct{}{},
		importTimeField: struct{}{},
		latestField:     struct{}{},
	}

	// map of valid fields and related header name
	ImagesFieldHeaderMap = map[string]string{
		keyField:        "KEY",
		nameField:       "NAME",
		importTimeField: "IMPORTTIME",
		latestField:     "LATEST",
	}

	// map of valid sort fields containing the mapping between the provided field name
	// and the related aciinfo's field name.
	ImagesFieldAciInfoMap = map[string]string{
		keyField:        "blobkey",
		nameField:       "name",
		importTimeField: "importtime",
		latestField:     "latest",
	}

	ImagesSortableFields = map[string]struct{}{
		nameField:       struct{}{},
		importTimeField: struct{}{},
	}
)

type ImagesFields []string

func (ifs *ImagesFields) Set(s string) error {
	*ifs = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := imagesAllFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*ifs = append(*ifs, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (ifs *ImagesFields) String() string {
	return strings.Join(*ifs, ",")
}

func (ifs *ImagesFields) Type() string {
	return "imagesFields"
}

type ImagesSortFields []string

func (isf *ImagesSortFields) Set(s string) error {
	*isf = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := ImagesSortableFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*isf = append(*isf, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (isf *ImagesSortFields) String() string {
	return strings.Join(*isf, ",")
}

func (isf *ImagesSortFields) Type() string {
	return "imagesSortFields"
}

type ImagesSortAsc bool

func (isa *ImagesSortAsc) Set(s string) error {
	switch s {
	case "asc":
		*isa = true
	case "desc":
		*isa = false
	default:
		return fmt.Errorf("wrong sort order")
	}
	return nil
}

func (isa *ImagesSortAsc) String() string {
	if *isa {
		return "asc"
	}
	return "desc"
}

func (isa *ImagesSortAsc) Type() string {
	return "imagesSortAsc"
}

var (
	cmdImageList = &cobra.Command{
		Use:   "list",
		Short: "List images in the local store",
		Run:   runWrapper(runImages),
	}
	flagImagesFields     ImagesFields
	flagImagesSortFields ImagesSortFields
	flagImagesSortAsc    ImagesSortAsc
)

func init() {
	// Set defaults
	flagImagesFields = []string{keyField, nameField, importTimeField, latestField}
	flagImagesSortFields = []string{importTimeField}
	flagImagesSortAsc = true

	cmdImage.AddCommand(cmdImageList)
	cmdImageList.Flags().Var(&flagImagesFields, "fields", `comma separated list of fields to display. Accepted values: "key", "name", "importtime", "latest"`)
	cmdImageList.Flags().Var(&flagImagesSortFields, "sort", `sort the output according to the provided comma separated list of fields. Accepted values: "name", "importtime"`)
	cmdImageList.Flags().Var(&flagImagesSortAsc, "order", `choose the sorting order if at least one sort field is provided (--sort). Accepted values: "asc", "desc"`)
	cmdImageList.Flags().BoolVar(&flagNoLegend, "no-legend", false, "suppress a legend with the list")
	cmdImageList.Flags().BoolVar(&flagFullOutput, "full", false, "Use long output format")
}

func runImages(cmd *cobra.Command, args []string) (exit int) {
	if !flagNoLegend {
		headerFields := []string{}
		for _, f := range flagImagesFields {
			headerFields = append(headerFields, ImagesFieldHeaderMap[f])
		}
		fmt.Fprintf(tabOut, "%s\n", strings.Join(headerFields, "\t"))
	}

	s, err := store.NewStore(globalFlags.Dir)
	if err != nil {
		stderr("images: cannot open store: %v\n", err)
		return 1
	}

	sortAciinfoFields := []string{}
	for _, f := range flagImagesSortFields {
		sortAciinfoFields = append(sortAciinfoFields, ImagesFieldAciInfoMap[f])
	}
	aciInfos, err := s.GetAllACIInfos(sortAciinfoFields, bool(flagImagesSortAsc))
	if err != nil {
		stderr("images: unable to get aci infos: %v", err)
		return
	}

	for _, aciInfo := range aciInfos {
		im, err := s.GetImageManifest(aciInfo.BlobKey)
		if err != nil {
			// ignore aciInfo with missing image manifest as it can be deleted in the meantime
			continue
		}
		version, ok := im.Labels.Get("version")
		for _, f := range flagImagesFields {
			switch f {
			case keyField:
				hashKey := aciInfo.BlobKey
				if !flagFullOutput {
					// The short hash form is [HASH_ALGO]-[FIRST 12 CHAR]
					// For example, sha512-123456789012
					pos := strings.Index(hashKey, "-")
					trimLength := pos + 13
					if pos > 0 && trimLength < len(hashKey) {
						hashKey = hashKey[:trimLength]
					}
				}
				fmt.Fprintf(tabOut, "%s", hashKey)
			case nameField:
				fmt.Fprintf(tabOut, "%s", aciInfo.Name)
				if ok {
					fmt.Fprintf(tabOut, ":%s", version)
				}
			case importTimeField:
				if flagFullOutput {
					fmt.Fprintf(tabOut, "%s", aciInfo.ImportTime.Format(defaultTimeLayout))
				} else {
					fmt.Fprintf(tabOut, "%s", humanize.Time(aciInfo.ImportTime))
				}
			case latestField:
				fmt.Fprintf(tabOut, "%t", aciInfo.Latest)
			}
			fmt.Fprintf(tabOut, "\t")

		}
		fmt.Fprintf(tabOut, "\n")
	}

	tabOut.Flush()
	return 0
}
<commit_msg>rkt: make image import time header more readable<commit_after>// Copyright 2015 The rkt Authors
//
// 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 main

import (
	"fmt"
	"strings"

	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/dustin/go-humanize"
	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/spf13/cobra"
	"github.com/coreos/rkt/store"
)

const (
	defaultTimeLayout = "2006-01-02 15:04:05.999 -0700 MST"

	keyField        = "key"
	nameField       = "name"
	importTimeField = "importtime"
	latestField     = "latest"
)

var (
	// map of valid fields and related flag value
	imagesAllFields = map[string]struct{}{
		keyField:        struct{}{},
		nameField:       struct{}{},
		importTimeField: struct{}{},
		latestField:     struct{}{},
	}

	// map of valid fields and related header name
	ImagesFieldHeaderMap = map[string]string{
		keyField:        "KEY",
		nameField:       "NAME",
		importTimeField: "IMPORT TIME",
		latestField:     "LATEST",
	}

	// map of valid sort fields containing the mapping between the provided field name
	// and the related aciinfo's field name.
	ImagesFieldAciInfoMap = map[string]string{
		keyField:        "blobkey",
		nameField:       "name",
		importTimeField: "importtime",
		latestField:     "latest",
	}

	ImagesSortableFields = map[string]struct{}{
		nameField:       struct{}{},
		importTimeField: struct{}{},
	}
)

type ImagesFields []string

func (ifs *ImagesFields) Set(s string) error {
	*ifs = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := imagesAllFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*ifs = append(*ifs, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (ifs *ImagesFields) String() string {
	return strings.Join(*ifs, ",")
}

func (ifs *ImagesFields) Type() string {
	return "imagesFields"
}

type ImagesSortFields []string

func (isf *ImagesSortFields) Set(s string) error {
	*isf = []string{}
	fields := strings.Split(s, ",")
	seen := map[string]struct{}{}
	for _, f := range fields {
		// accept any case
		f = strings.ToLower(f)
		_, ok := ImagesSortableFields[f]
		if !ok {
			return fmt.Errorf("unknown field %q", f)
		}
		if _, ok := seen[f]; ok {
			return fmt.Errorf("duplicated field %q", f)
		}
		*isf = append(*isf, f)
		seen[f] = struct{}{}
	}

	return nil
}

func (isf *ImagesSortFields) String() string {
	return strings.Join(*isf, ",")
}

func (isf *ImagesSortFields) Type() string {
	return "imagesSortFields"
}

type ImagesSortAsc bool

func (isa *ImagesSortAsc) Set(s string) error {
	switch s {
	case "asc":
		*isa = true
	case "desc":
		*isa = false
	default:
		return fmt.Errorf("wrong sort order")
	}
	return nil
}

func (isa *ImagesSortAsc) String() string {
	if *isa {
		return "asc"
	}
	return "desc"
}

func (isa *ImagesSortAsc) Type() string {
	return "imagesSortAsc"
}

var (
	cmdImageList = &cobra.Command{
		Use:   "list",
		Short: "List images in the local store",
		Run:   runWrapper(runImages),
	}
	flagImagesFields     ImagesFields
	flagImagesSortFields ImagesSortFields
	flagImagesSortAsc    ImagesSortAsc
)

func init() {
	// Set defaults
	flagImagesFields = []string{keyField, nameField, importTimeField, latestField}
	flagImagesSortFields = []string{importTimeField}
	flagImagesSortAsc = true

	cmdImage.AddCommand(cmdImageList)
	cmdImageList.Flags().Var(&flagImagesFields, "fields", `comma separated list of fields to display. Accepted values: "key", "name", "importtime", "latest"`)
	cmdImageList.Flags().Var(&flagImagesSortFields, "sort", `sort the output according to the provided comma separated list of fields. Accepted values: "name", "importtime"`)
	cmdImageList.Flags().Var(&flagImagesSortAsc, "order", `choose the sorting order if at least one sort field is provided (--sort). Accepted values: "asc", "desc"`)
	cmdImageList.Flags().BoolVar(&flagNoLegend, "no-legend", false, "suppress a legend with the list")
	cmdImageList.Flags().BoolVar(&flagFullOutput, "full", false, "Use long output format")
}

func runImages(cmd *cobra.Command, args []string) (exit int) {
	if !flagNoLegend {
		headerFields := []string{}
		for _, f := range flagImagesFields {
			headerFields = append(headerFields, ImagesFieldHeaderMap[f])
		}
		fmt.Fprintf(tabOut, "%s\n", strings.Join(headerFields, "\t"))
	}

	s, err := store.NewStore(globalFlags.Dir)
	if err != nil {
		stderr("images: cannot open store: %v\n", err)
		return 1
	}

	sortAciinfoFields := []string{}
	for _, f := range flagImagesSortFields {
		sortAciinfoFields = append(sortAciinfoFields, ImagesFieldAciInfoMap[f])
	}
	aciInfos, err := s.GetAllACIInfos(sortAciinfoFields, bool(flagImagesSortAsc))
	if err != nil {
		stderr("images: unable to get aci infos: %v", err)
		return
	}

	for _, aciInfo := range aciInfos {
		im, err := s.GetImageManifest(aciInfo.BlobKey)
		if err != nil {
			// ignore aciInfo with missing image manifest as it can be deleted in the meantime
			continue
		}
		version, ok := im.Labels.Get("version")
		for _, f := range flagImagesFields {
			switch f {
			case keyField:
				hashKey := aciInfo.BlobKey
				if !flagFullOutput {
					// The short hash form is [HASH_ALGO]-[FIRST 12 CHAR]
					// For example, sha512-123456789012
					pos := strings.Index(hashKey, "-")
					trimLength := pos + 13
					if pos > 0 && trimLength < len(hashKey) {
						hashKey = hashKey[:trimLength]
					}
				}
				fmt.Fprintf(tabOut, "%s", hashKey)
			case nameField:
				fmt.Fprintf(tabOut, "%s", aciInfo.Name)
				if ok {
					fmt.Fprintf(tabOut, ":%s", version)
				}
			case importTimeField:
				if flagFullOutput {
					fmt.Fprintf(tabOut, "%s", aciInfo.ImportTime.Format(defaultTimeLayout))
				} else {
					fmt.Fprintf(tabOut, "%s", humanize.Time(aciInfo.ImportTime))
				}
			case latestField:
				fmt.Fprintf(tabOut, "%t", aciInfo.Latest)
			}
			fmt.Fprintf(tabOut, "\t")

		}
		fmt.Fprintf(tabOut, "\n")
	}

	tabOut.Flush()
	return 0
}
<|endoftext|>


<commit_before>package controllers

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"net/http"
	"net/mail"
	"text/template"
	"time"

	"github.com/coduno/api/model"
	"github.com/coduno/api/util/passenger"
	"github.com/coduno/api/util/password"
	"google.golang.org/appengine/datastore"
	appmail "google.golang.org/appengine/mail"

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

var invitation *template.Template

func init() {
	var err error
	invitation, err = template.ParseFiles("./mail/template.invitation")
	if err != nil {
		panic(err)
	}
}

// Invitation handles the creation of a new invitation and sends an e-mail to
// the user.
func Invitation(ctx context.Context, w http.ResponseWriter, r *http.Request) (status int, err error) {
	p, ok := passenger.FromContext(ctx)
	if !ok {
		return http.StatusUnauthorized, errors.New("permission denied")
	}
	cKey := p.UserKey.Parent()
	if cKey == nil {
		return http.StatusUnauthorized, errors.New("permission denied")
	}
	var company model.Company
	datastore.Get(ctx, cKey, &company)

	// TODO(flowlo): Also check whether the parent of the current user is the
	// parent of the challenge (if any).

	if r.Method == "GET" {
		return http.StatusMethodNotAllowed, nil
	}

	var params = struct {
		Address   string
		Challenge *datastore.Key
	}{}
	if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
		return http.StatusBadRequest, err
	}

	address, err := mail.ParseAddress(params.Address)
	if err != nil {
		return http.StatusBadRequest, err
	}

	var users model.Users
	keys, err := model.NewQueryForUser().
		Filter("Address=", address.Address).
		KeysOnly().
		Limit(1).
		GetAll(ctx, &users)

	if err != nil {
		return http.StatusInternalServerError, err
	}

	var key *datastore.Key
	var user model.User
	if len(keys) == 1 {
		key = keys[0]
		user = users[0]
	} else {
		user = model.User{Address: *address}
		key, err = datastore.Put(ctx, key, &user)
		if err != nil {
			return http.StatusInternalServerError, err
		}
		profile := model.Profile{}
		if _, err = profile.SaveWithParent(ctx, key); err != nil {
			return http.StatusInternalServerError, err
		}
	}

	// TODO(flowlo): Generate token with its own util.
	tokenValue, err := password.Generate(0)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	now := time.Now()
	accessToken := model.AccessToken{
		Value:        string(tokenValue),
		Creation:     now,
		Modification: now,
		Expiry:       now.Add(time.Hour * 24 * 365),
		Description:  "Initialization Token",
	}

	token := base64.URLEncoding.EncodeToString([]byte(params.Challenge.Encode() + accessToken.Value))

	i := model.Invitation{
		User: key,
	}

	buf := new(bytes.Buffer)
	if err = invitation.Execute(buf, struct {
		UserAddress, CompanyAddress mail.Address
		Token                       string
	}{
		user.Address,
		company.Address,
		token,
	}); err != nil {
		return http.StatusInternalServerError, err
	}

	if err = appmail.Send(ctx, &appmail.Message{
		Sender:  "Lorenz Leutgeb <lorenz.leutgeb@cod.uno>",
		To:      []string{user.Address.String()},
		Subject: "We challenge you!",
		Body:    buf.String(),
	}); err != nil {
		return http.StatusInternalServerError, err
	}

	key, err = i.Save(ctx)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	json.NewEncoder(w).Encode(i.Key(key))
	return http.StatusOK, nil
}
<commit_msg>ctrl: Fix zero key in invitation controller<commit_after>package controllers

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/mail"
	"text/template"
	"time"

	"github.com/coduno/api/model"
	"github.com/coduno/api/util/passenger"
	"github.com/coduno/api/util/password"
	"google.golang.org/appengine/datastore"
	appmail "google.golang.org/appengine/mail"

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

var invitation *template.Template

func init() {
	var err error
	invitation, err = template.ParseFiles("./mail/template.invitation")
	if err != nil {
		panic(err)
	}
}

// Invitation handles the creation of a new invitation and sends an e-mail to
// the user.
func Invitation(ctx context.Context, w http.ResponseWriter, r *http.Request) (status int, err error) {
	if r.Method != "POST" {
		return http.StatusMethodNotAllowed, nil
	}

	p, ok := passenger.FromContext(ctx)
	if !ok {
		return http.StatusUnauthorized, nil
	}

	cKey := p.UserKey.Parent()
	if cKey == nil {
		return http.StatusUnauthorized, nil
	}

	var company model.Company
	if err = datastore.Get(ctx, cKey, &company); err != nil {
		return http.StatusInternalServerError, err
	}

	var params = struct {
		Address, Challenge string
	}{}

	if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
		return http.StatusBadRequest, err
	}

	address, err := mail.ParseAddress(params.Address)
	if err != nil {
		return http.StatusBadRequest, err
	}

	// TODO(flowlo): Check whether the parent of the current user is the
	// parent of the challenge (if any), and check whether the challenge
	// even exists.

	var users model.Users
	keys, err := model.NewQueryForUser().
		Filter("Address=", address.Address).
		KeysOnly().
		Limit(1).
		GetAll(ctx, &users)

	if err != nil {
		return http.StatusInternalServerError, err
	}

	var key *datastore.Key
	var user model.User
	if len(keys) == 1 {
		key = keys[0]
		user = users[0]
	} else {
		user = model.User{Address: *address}
		key, err = user.Save(ctx)
		if err != nil {
			return http.StatusInternalServerError, err
		}
		profile := model.Profile{}
		if _, err = profile.SaveWithParent(ctx, key); err != nil {
			return http.StatusInternalServerError, err
		}
	}

	// TODO(flowlo): Generate token with its own util.
	tokenValue, err := password.Generate(0)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	now := time.Now()
	accessToken := model.AccessToken{
		Value:        string(tokenValue),
		Creation:     now,
		Modification: now,
		Expiry:       now.Add(time.Hour * 24 * 365),
		Description:  "Initialization Token",
	}

	token := base64.URLEncoding.EncodeToString([]byte(params.Challenge + accessToken.Value))

	i := model.Invitation{
		User: key,
	}

	buf := new(bytes.Buffer)
	if err = invitation.Execute(buf, struct {
		UserAddress, CompanyAddress mail.Address
		Token                       string
	}{
		user.Address,
		company.Address,
		token,
	}); err != nil {
		return http.StatusInternalServerError, err
	}

	if err = appmail.Send(ctx, &appmail.Message{
		Sender:  "Lorenz Leutgeb <lorenz.leutgeb@cod.uno>",
		To:      []string{user.Address.String()},
		Subject: "We challenge you!",
		Body:    buf.String(),
	}); err != nil {
		return http.StatusInternalServerError, err
	}

	key, err = i.Save(ctx)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	json.NewEncoder(w).Encode(i.Key(key))
	return http.StatusOK, nil
}
<|endoftext|>


<commit_before>package model

import (
	"database/sql"
	"fmt"
	"log"
	"reflect"
	"time"

	"github.com/jmoiron/sqlx"
	"github.com/patrickmn/go-cache"
	"github.com/pkg/errors"
)

// SegmentStorage represents interface to get segment related data.
type SegmentStorage interface {
	// Get returns instance of Segment based on the given code.
	Get(code string) (*Segment, bool, error)
	// List returns all available segments configured via Beam admin.
	List() (SegmentCollection, error)
	// CheckUser verifies presence of user within provided segment.
	CheckUser(segment *Segment, userID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error)
	// CheckBrowser verifies presence of browser within provided segment.
	CheckBrowser(segment *Segment, browserID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error)
	// Users return list of all users within segment.
	Users(segment *Segment, now time.Time, ro RuleOverrides) ([]string, error)
	// EventRules returns map of rules assigned to given "category/event" key.
	EventRules() EventRules
	// OverridableFields returns array of fields that expect to be overriden when rule is checked.
	OverridableFields() OverridableFields
	// Flags returns array of flags available for rules.
	Flags() Flags
}

// EventRules represent map of rules with given "category/event" assigned
type EventRules map[string][]int

// OverridableFields represent array of fields (key-value pairs) keyed by Rule ID.
type OverridableFields map[int][]string

// Flags represent array of flags keyed by Rule ID.
type Flags map[int]map[string]string

// SegmentCache represents event count information for SegmentRules indexed by SegmentRule ID.
type SegmentCache map[int]*SegmentRuleCache

// Segment structure.
type Segment struct {
	ID             int
	Code           string
	Name           string
	Active         bool
	CreatedAt      time.Time `db:"created_at"`
	UpdatedAt      time.Time `db:"updated_at"`
	SegmentGroupID int       `db:"segment_group_id"`

	Group SegmentGroup  `db:"segment_groups"`
	Rules []SegmentRule `db:"segment_rules"`
}

// SegmentCollection is list of Segments.
type SegmentCollection []*Segment

// SegmentGroup represents metadata about group, in which Segments can be placed in.
type SegmentGroup struct {
	ID        int
	Name      string
	Code      string
	Type      string
	Sorting   int
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

// User represents information about User in Segment.
type User struct {
	ID    string
	Email string
}

// UserCollection is list of Users.
type UserCollection []*User

// UserSet is set of Users keyed by userID.
type UserSet map[string]bool

// BrowserSet is set of Browser keyed by browserID.
type BrowserSet map[string]bool

// Intersector responds to ability to intersect provided userID with some other collection structure.
type Intersector func(userID string) bool

// SegmentDB represents Segment's storage MySQL/InfluxDB implementation.
type SegmentDB struct {
	MySQL                    *sqlx.DB
	RuleCountCache           *cache.Cache
	EventStorage             EventStorage
	PageviewStorage          PageviewStorage
	CommerceStorage          CommerceStorage
	Segments                 map[string]*Segment
	ExplicitSegmentsUsers    map[string]UserSet
	ExplicitSegmentsBrowsers map[string]BrowserSet
}

// Get returns instance of Segment based on the given code.
func (sDB *SegmentDB) Get(code string) (*Segment, bool, error) {
	p, ok := sDB.Segments[code]
	if ok {
		return p, true, nil
	}

	s := &Segment{}
	err := sDB.MySQL.Get(s, "SELECT * FROM segments WHERE code = ?", code)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, false, nil
		}
		return nil, false, errors.Wrap(err, "unable to get segment from MySQL")
	}

	src := []SegmentRule{}
	err = sDB.MySQL.Select(&src, "SELECT * FROM segment_rules WHERE segment_id = ?", s.ID)
	if err != nil {
		if err != sql.ErrNoRows {
			return nil, false, errors.Wrap(err, fmt.Sprintf("unable to get related segment rules for segment [%d]", s.ID))
		}
	}
	s.Rules = src

	return s, true, nil
}

// List returns all available segments configured via Beam admin.
func (sDB *SegmentDB) List() (SegmentCollection, error) {
	sc := SegmentCollection{}
	err := sDB.MySQL.Select(&sc, "SELECT name, code FROM segments")
	if err != nil {
		return nil, err
	}
	return sc, nil
}

// CheckUser verifies presence of user within provided segment.
func (sDB *SegmentDB) CheckUser(segment *Segment, userID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error) {
	if segment.Group.Type == "explicit" {
		segmentUsers := sDB.ExplicitSegmentsUsers[segment.Code]
		_, ok := segmentUsers[userID]
		return cache, ok, nil
	}
	return sDB.check(segment, "user_id", userID, now, cache, ro)
}

// CheckBrowser verifies presence of browser within provided segment.
func (sDB *SegmentDB) CheckBrowser(segment *Segment, browserID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error) {
	if segment.Group.Type == "explicit" {
		log.Printf("checkin browser %s", browserID)
		segmentBrowsers := sDB.ExplicitSegmentsBrowsers[segment.Code]
		_, ok := segmentBrowsers[browserID]
		return cache, ok, nil
	}
	return sDB.check(segment, "browser_id", browserID, now, cache, ro)
}

// Check verifies presence of provided tag within segment by its value.
func (sDB *SegmentDB) check(segment *Segment, tagName, tagValue string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error) {
	c := make(SegmentCache)

	for _, sr := range segment.Rules {
		osr := sr.applyOverrides(ro)
		cacheKey := sr.getCacheKey(ro)

		// get count
		var count int
		var err error
		if src, ok := cache[cacheKey]; ok {
			count = src.Count
			// update cache
			c[cacheKey] = &SegmentRuleCache{
				Count:    count,
				SyncedAt: cache[cacheKey].SyncedAt,
			}
		} else {
			count, err = sDB.getRuleEventCount(osr, tagName, tagValue, now, ro)
			if err != nil {
				return nil, false, errors.Wrap(err, "unable to get SegmentRule event count")
			}
			// set synced cache
			c[cacheKey] = &SegmentRuleCache{
				Count:    count,
				SyncedAt: now,
			}
		}

		// evaluate
		ok, err := osr.Evaluate(count)
		if err != nil {
			return nil, false, errors.Wrap(err, "unable to evaluate SegmentRule")
		}

		// if user doesn't match current rule, no need to evaluate further
		if !ok {
			return c, ok, nil
		}
	}

	return c, true, nil
}

// getRuleEventCount returns real db-based number of events occurred based on provided SegmentRule.
func (sDB *SegmentDB) getRuleEventCount(sr *SegmentRule, tagName, tagValue string, now time.Time, ro RuleOverrides) (int, error) {
	options := sr.options(now, ro)
	options.FilterBy = append(options.FilterBy, &FilterBy{tagName, []string{tagValue}})

	var crc CountRowCollection
	var ok bool
	var err error

	switch sr.EventCategory {
	case CategoryPageview:
		crc, ok, err = sDB.PageviewStorage.Count(options)
	case CategoryCommerce:
		crc, ok, err = sDB.CommerceStorage.Count(options)
	default:
		crc, ok, err = sDB.EventStorage.Count(options)
	}

	if err != nil {
		return 0, errors.Wrap(err, "unable to get rule event count")
	}
	if !ok {
		return 0, nil
	}

	// result read
	if len(crc) > 1 {
		return 0, fmt.Errorf("unexpected result of CountRows returned: %d", len(crc))
	}

	return crc[0].Count, nil
}

// Users return list of all users within segment.
func (sDB *SegmentDB) Users(segment *Segment, now time.Time, ro RuleOverrides) ([]string, error) {
	if segment.Group.Type == "explicit" {
		uc := []string{}
		for userID := range sDB.ExplicitSegmentsUsers[segment.Code] {
			uc = append(uc, userID)
		}
		return uc, nil
	}

	users := make(UserSet)

	for i, sr := range segment.Rules {
		filteredUsers, err := sDB.ruleUsers(sr, now, ro, func(userID string) bool {
			// on first iteration everyone is eligible to be in "users"
			if i == 0 {
				return true
			}
			// on further iterations user needs to be present in "users" (effectively all previous iterations)
			_, ok := users[userID]
			return ok
		})
		if err != nil {
			return nil, err
		}
		users = filteredUsers
	}

	uc := []string{}
	for userID := range users {
		uc = append(uc, userID)
	}

	return uc, nil
}

// ruleUsers lists all users based on SegmentRule and filters them based on the provided Intersector.
func (sDB *SegmentDB) ruleUsers(sr SegmentRule, now time.Time, ro RuleOverrides, intersect Intersector) (UserSet, error) {
	options := sr.options(now, ro)
	options.GroupBy = append(options.GroupBy, "user_id")

	um := make(UserSet)

	var crc CountRowCollection
	var ok bool
	var err error

	switch sr.EventCategory {
	case CategoryPageview:
		crc, ok, err = sDB.PageviewStorage.Count(options)
	case CategoryCommerce:
		crc, ok, err = sDB.CommerceStorage.Count(options)
	default:
		crc, ok, err = sDB.EventStorage.Count(options)
	}

	if err != nil {
		return nil, errors.Wrap(err, "unable to get rule event count")
	}
	if !ok {
		return um, nil
	}

	for _, cr := range crc {
		evalResult, err := sr.Evaluate(cr.Count)
		if err != nil {
			return nil, err
		}
		if !evalResult {
			continue
		}
		userID := cr.Tags["user_id"]
		if intersect(userID) {
			um[userID] = true
		}
	}

	return um, nil
}

// Cache stores the segments in memory.
func (sDB *SegmentDB) Cache() error {
	sm := make(map[string]*Segment)
	sc := SegmentCollection{}

	err := sDB.MySQL.Select(&sc, "SELECT * FROM segments WHERE active = 1")
	if err != nil {
		if err == sql.ErrNoRows {
			return nil
		}
		return errors.Wrap(err, "unable to cache segments from MySQL")
	}

	for _, s := range sc {
		src := []SegmentRule{}
		err = sDB.MySQL.Select(&src, "SELECT * FROM segment_rules WHERE segment_id = ?", s.ID)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("unable to get related segment rules for segment [%d]", s.ID))
		}
		s.Rules = src
	}

	for _, s := range sc {
		src := SegmentGroup{}
		err = sDB.MySQL.Get(&src, "SELECT * FROM segment_groups WHERE id = ?", s.SegmentGroupID)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("unable to get related segment group for segment [%d]", s.ID))
		}
		s.Group = src
	}

	old := sDB.Segments
	for _, s := range sc {
		sm[s.Code] = s
	}
	sDB.Segments = sm

	if !reflect.DeepEqual(old, sm) {
		log.Println("segment cache reloaded")
	}
	return nil
}

// CacheExplicitSegments caches segments data in memory
func (sDB *SegmentDB) CacheExplicitSegments() error {
	usersSet := make(map[string]UserSet)
	browsersSet := make(map[string]BrowserSet)

	for code, s := range sDB.Segments {
		if s.Group.Type == "explicit" {
			// load users
			rows, err := sDB.MySQL.Query("SELECT user_id FROM segment_users WHERE segment_id = ?", s.ID)
			if err != nil {
				if err == sql.ErrNoRows {
					continue
				}
				return errors.Wrap(err, "unable to get segment users from MySQL")
			}
			users := make(UserSet)
			for rows.Next() {
				var userID string
				err = rows.Scan(&userID)
				if err != nil {
					return errors.Wrap(err, "unable to load user ID from result row")
				}
				users[userID] = true
			}
			usersSet[code] = users

			// load browsers
			rows, err = sDB.MySQL.Query("SELECT browser_id FROM segment_browsers WHERE segment_id = ?", s.ID)
			if err != nil {
				if err == sql.ErrNoRows {
					continue
				}
				return errors.Wrap(err, "unable to get segment browsers from MySQL")
			}
			browsers := make(BrowserSet)
			for rows.Next() {
				var browserID string
				err = rows.Scan(&browserID)
				if err != nil {
					return errors.Wrap(err, "unable to load browser ID from result row")
				}
				browsers[browserID] = true
			}
			browsersSet[code] = browsers
		}
	}
	sDB.ExplicitSegmentsUsers = usersSet
	sDB.ExplicitSegmentsBrowsers = browsersSet
	return nil
}

// EventRules returns map of rules assigned to given "category/event" key
func (sDB *SegmentDB) EventRules() EventRules {
	er := make(EventRules)
	for _, s := range sDB.Segments {
		for _, sr := range s.Rules {
			key := fmt.Sprintf("%s/%s", sr.EventCategory, sr.EventAction)
			er[key] = append(er[key], sr.ID)
		}
	}
	return er
}

// OverridableFields returns array of fields that expect to be overriden when rule is checked.
func (sDB *SegmentDB) OverridableFields() OverridableFields {
	of := make(OverridableFields)
	for _, s := range sDB.Segments {
		for _, sr := range s.Rules {
			of[sr.ID] = sr.overridableFields()
		}
	}
	return of
}

// Flags returns array of flags available for rules.
func (sDB *SegmentDB) Flags() Flags {
	flags := make(Flags)
	for _, s := range sDB.Segments {
		for _, sr := range s.Rules {
			flags[sr.ID] = sr.flags()
		}
	}
	return flags
}
<commit_msg>Listing segments with associated group<commit_after>package model

import (
	"database/sql"
	"fmt"
	"log"
	"reflect"
	"time"

	"github.com/jmoiron/sqlx"
	"github.com/patrickmn/go-cache"
	"github.com/pkg/errors"
)

// SegmentStorage represents interface to get segment related data.
type SegmentStorage interface {
	// Get returns instance of Segment based on the given code.
	Get(code string) (*Segment, bool, error)
	// List returns all available segments configured via Beam admin.
	List() (SegmentCollection, error)
	// CheckUser verifies presence of user within provided segment.
	CheckUser(segment *Segment, userID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error)
	// CheckBrowser verifies presence of browser within provided segment.
	CheckBrowser(segment *Segment, browserID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error)
	// Users return list of all users within segment.
	Users(segment *Segment, now time.Time, ro RuleOverrides) ([]string, error)
	// EventRules returns map of rules assigned to given "category/event" key.
	EventRules() EventRules
	// OverridableFields returns array of fields that expect to be overriden when rule is checked.
	OverridableFields() OverridableFields
	// Flags returns array of flags available for rules.
	Flags() Flags
}

// EventRules represent map of rules with given "category/event" assigned
type EventRules map[string][]int

// OverridableFields represent array of fields (key-value pairs) keyed by Rule ID.
type OverridableFields map[int][]string

// Flags represent array of flags keyed by Rule ID.
type Flags map[int]map[string]string

// SegmentCache represents event count information for SegmentRules indexed by SegmentRule ID.
type SegmentCache map[int]*SegmentRuleCache

// Segment structure.
type Segment struct {
	ID             int
	Code           string
	Name           string
	Active         bool
	CreatedAt      time.Time `db:"created_at"`
	UpdatedAt      time.Time `db:"updated_at"`
	SegmentGroupID int       `db:"segment_group_id"`

	Group SegmentGroup  `db:"segment_group"`
	Rules []SegmentRule `db:"segment_rules"`
}

// SegmentCollection is list of Segments.
type SegmentCollection []*Segment

// SegmentGroup represents metadata about group, in which Segments can be placed in.
type SegmentGroup struct {
	ID        int
	Name      string
	Code      string
	Type      string
	Sorting   int
	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

// User represents information about User in Segment.
type User struct {
	ID    string
	Email string
}

// UserCollection is list of Users.
type UserCollection []*User

// UserSet is set of Users keyed by userID.
type UserSet map[string]bool

// BrowserSet is set of Browser keyed by browserID.
type BrowserSet map[string]bool

// Intersector responds to ability to intersect provided userID with some other collection structure.
type Intersector func(userID string) bool

// SegmentDB represents Segment's storage MySQL/InfluxDB implementation.
type SegmentDB struct {
	MySQL                    *sqlx.DB
	RuleCountCache           *cache.Cache
	EventStorage             EventStorage
	PageviewStorage          PageviewStorage
	CommerceStorage          CommerceStorage
	Segments                 map[string]*Segment
	ExplicitSegmentsUsers    map[string]UserSet
	ExplicitSegmentsBrowsers map[string]BrowserSet
}

// Get returns instance of Segment based on the given code.
func (sDB *SegmentDB) Get(code string) (*Segment, bool, error) {
	p, ok := sDB.Segments[code]
	if ok {
		return p, true, nil
	}

	s := &Segment{}
	err := sDB.MySQL.Get(s, "SELECT * FROM segments WHERE code = ?", code)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, false, nil
		}
		return nil, false, errors.Wrap(err, "unable to get segment from MySQL")
	}

	src := []SegmentRule{}
	err = sDB.MySQL.Select(&src, "SELECT * FROM segment_rules WHERE segment_id = ?", s.ID)
	if err != nil {
		if err != sql.ErrNoRows {
			return nil, false, errors.Wrap(err, fmt.Sprintf("unable to get related segment rules for segment [%d]", s.ID))
		}
	}
	s.Rules = src

	return s, true, nil
}

// List returns all available segments configured via Beam admin.
func (sDB *SegmentDB) List() (SegmentCollection, error) {
	sc := SegmentCollection{}
	err := sDB.MySQL.Select(&sc, "SELECT segments.name, segments.code, segments.segment_group_id, "+
		"segment_groups.id AS 'segment_group.id', "+
		"segment_groups.name AS 'segment_group.name', "+
		"segment_groups.code AS 'segment_group.code', "+
		"segment_groups.type AS 'segment_group.type', "+
		"segment_groups.sorting AS 'segment_group.sorting' "+
		"FROM segments JOIN segment_groups ON segments.segment_group_id = segment_groups.id")
	if err != nil {
		return nil, err
	}
	return sc, nil
}

// CheckUser verifies presence of user within provided segment.
func (sDB *SegmentDB) CheckUser(segment *Segment, userID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error) {
	if segment.Group.Type == "explicit" {
		segmentUsers := sDB.ExplicitSegmentsUsers[segment.Code]
		_, ok := segmentUsers[userID]
		return cache, ok, nil
	}
	return sDB.check(segment, "user_id", userID, now, cache, ro)
}

// CheckBrowser verifies presence of browser within provided segment.
func (sDB *SegmentDB) CheckBrowser(segment *Segment, browserID string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error) {
	if segment.Group.Type == "explicit" {
		log.Printf("checkin browser %s", browserID)
		segmentBrowsers := sDB.ExplicitSegmentsBrowsers[segment.Code]
		_, ok := segmentBrowsers[browserID]
		return cache, ok, nil
	}
	return sDB.check(segment, "browser_id", browserID, now, cache, ro)
}

// Check verifies presence of provided tag within segment by its value.
func (sDB *SegmentDB) check(segment *Segment, tagName, tagValue string, now time.Time, cache SegmentCache, ro RuleOverrides) (SegmentCache, bool, error) {
	c := make(SegmentCache)

	for _, sr := range segment.Rules {
		osr := sr.applyOverrides(ro)
		cacheKey := sr.getCacheKey(ro)

		// get count
		var count int
		var err error
		if src, ok := cache[cacheKey]; ok {
			count = src.Count
			// update cache
			c[cacheKey] = &SegmentRuleCache{
				Count:    count,
				SyncedAt: cache[cacheKey].SyncedAt,
			}
		} else {
			count, err = sDB.getRuleEventCount(osr, tagName, tagValue, now, ro)
			if err != nil {
				return nil, false, errors.Wrap(err, "unable to get SegmentRule event count")
			}
			// set synced cache
			c[cacheKey] = &SegmentRuleCache{
				Count:    count,
				SyncedAt: now,
			}
		}

		// evaluate
		ok, err := osr.Evaluate(count)
		if err != nil {
			return nil, false, errors.Wrap(err, "unable to evaluate SegmentRule")
		}

		// if user doesn't match current rule, no need to evaluate further
		if !ok {
			return c, ok, nil
		}
	}

	return c, true, nil
}

// getRuleEventCount returns real db-based number of events occurred based on provided SegmentRule.
func (sDB *SegmentDB) getRuleEventCount(sr *SegmentRule, tagName, tagValue string, now time.Time, ro RuleOverrides) (int, error) {
	options := sr.options(now, ro)
	options.FilterBy = append(options.FilterBy, &FilterBy{tagName, []string{tagValue}})

	var crc CountRowCollection
	var ok bool
	var err error

	switch sr.EventCategory {
	case CategoryPageview:
		crc, ok, err = sDB.PageviewStorage.Count(options)
	case CategoryCommerce:
		crc, ok, err = sDB.CommerceStorage.Count(options)
	default:
		crc, ok, err = sDB.EventStorage.Count(options)
	}

	if err != nil {
		return 0, errors.Wrap(err, "unable to get rule event count")
	}
	if !ok {
		return 0, nil
	}

	// result read
	if len(crc) > 1 {
		return 0, fmt.Errorf("unexpected result of CountRows returned: %d", len(crc))
	}

	return crc[0].Count, nil
}

// Users return list of all users within segment.
func (sDB *SegmentDB) Users(segment *Segment, now time.Time, ro RuleOverrides) ([]string, error) {
	if segment.Group.Type == "explicit" {
		uc := []string{}
		for userID := range sDB.ExplicitSegmentsUsers[segment.Code] {
			uc = append(uc, userID)
		}
		return uc, nil
	}

	users := make(UserSet)

	for i, sr := range segment.Rules {
		filteredUsers, err := sDB.ruleUsers(sr, now, ro, func(userID string) bool {
			// on first iteration everyone is eligible to be in "users"
			if i == 0 {
				return true
			}
			// on further iterations user needs to be present in "users" (effectively all previous iterations)
			_, ok := users[userID]
			return ok
		})
		if err != nil {
			return nil, err
		}
		users = filteredUsers
	}

	uc := []string{}
	for userID := range users {
		uc = append(uc, userID)
	}

	return uc, nil
}

// ruleUsers lists all users based on SegmentRule and filters them based on the provided Intersector.
func (sDB *SegmentDB) ruleUsers(sr SegmentRule, now time.Time, ro RuleOverrides, intersect Intersector) (UserSet, error) {
	options := sr.options(now, ro)
	options.GroupBy = append(options.GroupBy, "user_id")

	um := make(UserSet)

	var crc CountRowCollection
	var ok bool
	var err error

	switch sr.EventCategory {
	case CategoryPageview:
		crc, ok, err = sDB.PageviewStorage.Count(options)
	case CategoryCommerce:
		crc, ok, err = sDB.CommerceStorage.Count(options)
	default:
		crc, ok, err = sDB.EventStorage.Count(options)
	}

	if err != nil {
		return nil, errors.Wrap(err, "unable to get rule event count")
	}
	if !ok {
		return um, nil
	}

	for _, cr := range crc {
		evalResult, err := sr.Evaluate(cr.Count)
		if err != nil {
			return nil, err
		}
		if !evalResult {
			continue
		}
		userID := cr.Tags["user_id"]
		if intersect(userID) {
			um[userID] = true
		}
	}

	return um, nil
}

// Cache stores the segments in memory.
func (sDB *SegmentDB) Cache() error {
	sm := make(map[string]*Segment)
	sc := SegmentCollection{}

	err := sDB.MySQL.Select(&sc, "SELECT * FROM segments WHERE active = 1")
	if err != nil {
		if err == sql.ErrNoRows {
			return nil
		}
		return errors.Wrap(err, "unable to cache segments from MySQL")
	}

	for _, s := range sc {
		src := []SegmentRule{}
		err = sDB.MySQL.Select(&src, "SELECT * FROM segment_rules WHERE segment_id = ?", s.ID)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("unable to get related segment rules for segment [%d]", s.ID))
		}
		s.Rules = src
	}

	for _, s := range sc {
		src := SegmentGroup{}
		err = sDB.MySQL.Get(&src, "SELECT * FROM segment_groups WHERE id = ?", s.SegmentGroupID)
		if err != nil {
			return errors.Wrap(err, fmt.Sprintf("unable to get related segment group for segment [%d]", s.ID))
		}
		s.Group = src
	}

	old := sDB.Segments
	for _, s := range sc {
		sm[s.Code] = s
	}
	sDB.Segments = sm

	if !reflect.DeepEqual(old, sm) {
		log.Println("segment cache reloaded")
	}
	return nil
}

// CacheExplicitSegments caches segments data in memory
func (sDB *SegmentDB) CacheExplicitSegments() error {
	usersSet := make(map[string]UserSet)
	browsersSet := make(map[string]BrowserSet)

	for code, s := range sDB.Segments {
		if s.Group.Type == "explicit" {
			// load users
			rows, err := sDB.MySQL.Query("SELECT user_id FROM segment_users WHERE segment_id = ?", s.ID)
			if err != nil {
				if err == sql.ErrNoRows {
					continue
				}
				return errors.Wrap(err, "unable to get segment users from MySQL")
			}
			users := make(UserSet)
			for rows.Next() {
				var userID string
				err = rows.Scan(&userID)
				if err != nil {
					return errors.Wrap(err, "unable to load user ID from result row")
				}
				users[userID] = true
			}
			usersSet[code] = users

			// load browsers
			rows, err = sDB.MySQL.Query("SELECT browser_id FROM segment_browsers WHERE segment_id = ?", s.ID)
			if err != nil {
				if err == sql.ErrNoRows {
					continue
				}
				return errors.Wrap(err, "unable to get segment browsers from MySQL")
			}
			browsers := make(BrowserSet)
			for rows.Next() {
				var browserID string
				err = rows.Scan(&browserID)
				if err != nil {
					return errors.Wrap(err, "unable to load browser ID from result row")
				}
				browsers[browserID] = true
			}
			browsersSet[code] = browsers
		}
	}
	sDB.ExplicitSegmentsUsers = usersSet
	sDB.ExplicitSegmentsBrowsers = browsersSet
	return nil
}

// EventRules returns map of rules assigned to given "category/event" key
func (sDB *SegmentDB) EventRules() EventRules {
	er := make(EventRules)
	for _, s := range sDB.Segments {
		for _, sr := range s.Rules {
			key := fmt.Sprintf("%s/%s", sr.EventCategory, sr.EventAction)
			er[key] = append(er[key], sr.ID)
		}
	}
	return er
}

// OverridableFields returns array of fields that expect to be overriden when rule is checked.
func (sDB *SegmentDB) OverridableFields() OverridableFields {
	of := make(OverridableFields)
	for _, s := range sDB.Segments {
		for _, sr := range s.Rules {
			of[sr.ID] = sr.overridableFields()
		}
	}
	return of
}

// Flags returns array of flags available for rules.
func (sDB *SegmentDB) Flags() Flags {
	flags := make(Flags)
	for _, s := range sDB.Segments {
		for _, sr := range s.Rules {
			flags[sr.ID] = sr.flags()
		}
	}
	return flags
}
<|endoftext|>


<commit_before>package cplayground

import (
	"sync"

	"github.com/ivan1993spb/snake-server/concurrent-map"
	"github.com/ivan1993spb/snake-server/engine"
)

func calcShardCount(size uint16) int {
	// TODO: Implement function.
	return 32
}

type Playground struct {
	cMap *cmap.ConcurrentMap

	entities    []*entity
	entitiesMux *sync.RWMutex

	area engine.Area
}

type ErrCreatePlayground struct {
	Err error
}

func (e ErrCreatePlayground) Error() string {
	return "cannot create playground: " + e.Error()
}

func NewPlayground(width, height uint8) (*Playground, error) {
	area, err := engine.NewArea(height, width)
	if err != nil {
		return nil, ErrCreatePlayground{err}
	}

	cMap, err := cmap.New(calcShardCount(area.Size()))
	if err != nil {
		return nil, ErrCreatePlayground{err}
	}

	return &Playground{
		cMap:        cMap,
		entities:    make([]*entity, 0),
		entitiesMux: &sync.RWMutex{},
		area:        area,
	}, nil
}

func (pg *Playground) unsafeObjectExists(object interface{}) bool {
	for i := range pg.entities {
		if pg.entities[i].GetObject() == object {
			return true
		}
	}
	return false
}

func (pg *Playground) ObjectExists(object interface{}) bool {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeObjectExists(object)
}

func (pg *Playground) unsafeLocationExists(location engine.Location) bool {
	for i := range pg.entities {
		if pg.entities[i].GetLocation().Equals(location) {
			return true
		}
	}
	return false
}

func (pg *Playground) LocationExists(location engine.Location) bool {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeLocationExists(location)
}

func (pg *Playground) unsafeEntityExists(object interface{}, location engine.Location) bool {
	for i := range pg.entities {
		if pg.entities[i].GetObject() == object && pg.entities[i].GetLocation().Equals(location) {
			return true
		}
	}
	return false
}

func (pg *Playground) EntityExists(object interface{}, location engine.Location) bool {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeEntityExists(object, location)
}

func (pg *Playground) unsafeGetObjectByLocation(location engine.Location) interface{} {
	for i := range pg.entities {
		if pg.entities[i].GetLocation().Equals(location) {
			return pg.entities[i].GetObject()
		}
	}
	return nil
}

func (pg *Playground) GetObjectByLocation(location engine.Location) interface{} {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeGetObjectByLocation(location)
}

func (pg *Playground) GetObjectByDot(dot engine.Dot) interface{} {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) GetEntityByDot(dot engine.Dot) (interface{}, engine.Location) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) GetObjectsByDots(dots []engine.Dot) []interface{} {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) CreateObject(object interface{}, location engine.Location) error {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) CreateObjectAvailableDots(object interface{}, location engine.Location) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) DeleteObject(object interface{}, location engine.Location) error {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) UpdateObject(object interface{}, old, new engine.Location) error {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) UpdateObjectAvailableDots(object interface{}, old, new engine.Location) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomDot(object interface{}) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomRect(object interface{}, rw, rh uint8) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomRectMargin(object interface{}, rw, rh, margin uint8) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomByDotsMask(object interface{}, dm *engine.DotsMask) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) Navigate(dot engine.Dot, dir engine.Direction, dis uint8) (engine.Dot, error) {
	return pg.area.Navigate(dot, dir, dis)
}

func (pg *Playground) Size() uint16 {
	return pg.area.Size()
}

func (pg *Playground) Width() uint8 {
	return pg.area.Width()
}

func (pg *Playground) Height() uint8 {
	return pg.area.Height()
}

func (pg *Playground) unsafeGetObjects() []interface{} {
	objects := make([]interface{}, len(pg.entities))
	for i, entity := range pg.entities {
		objects[i] = entity.GetObject()
	}
	return objects
}

func (pg *Playground) GetObjects() []interface{} {
	pg.entitiesMux.RLock()
	defer pg.entitiesMux.RUnlock()
	return pg.unsafeGetObjects()
}
<commit_msg>Implement for cplayground.Playground methods: GetObjectByDot, GetEntityByDot, GetObjectsByDots<commit_after>package cplayground

import (
	"sync"

	"github.com/ivan1993spb/snake-server/concurrent-map"
	"github.com/ivan1993spb/snake-server/engine"
)

func calcShardCount(size uint16) int {
	// TODO: Implement function.
	return 32
}

type Playground struct {
	cMap *cmap.ConcurrentMap

	entities    []*entity
	entitiesMux *sync.RWMutex

	area engine.Area
}

type ErrCreatePlayground struct {
	Err error
}

func (e ErrCreatePlayground) Error() string {
	return "cannot create playground: " + e.Error()
}

func NewPlayground(width, height uint8) (*Playground, error) {
	area, err := engine.NewArea(height, width)
	if err != nil {
		return nil, ErrCreatePlayground{err}
	}

	cMap, err := cmap.New(calcShardCount(area.Size()))
	if err != nil {
		return nil, ErrCreatePlayground{err}
	}

	return &Playground{
		cMap:        cMap,
		entities:    make([]*entity, 0),
		entitiesMux: &sync.RWMutex{},
		area:        area,
	}, nil
}

func (pg *Playground) unsafeObjectExists(object interface{}) bool {
	for i := range pg.entities {
		if pg.entities[i].GetObject() == object {
			return true
		}
	}
	return false
}

func (pg *Playground) ObjectExists(object interface{}) bool {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeObjectExists(object)
}

func (pg *Playground) unsafeLocationExists(location engine.Location) bool {
	for i := range pg.entities {
		if pg.entities[i].GetLocation().Equals(location) {
			return true
		}
	}
	return false
}

func (pg *Playground) LocationExists(location engine.Location) bool {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeLocationExists(location)
}

func (pg *Playground) unsafeEntityExists(object interface{}, location engine.Location) bool {
	for i := range pg.entities {
		if pg.entities[i].GetObject() == object && pg.entities[i].GetLocation().Equals(location) {
			return true
		}
	}
	return false
}

func (pg *Playground) EntityExists(object interface{}, location engine.Location) bool {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeEntityExists(object, location)
}

func (pg *Playground) unsafeGetObjectByLocation(location engine.Location) interface{} {
	for i := range pg.entities {
		if pg.entities[i].GetLocation().Equals(location) {
			return pg.entities[i].GetObject()
		}
	}
	return nil
}

func (pg *Playground) GetObjectByLocation(location engine.Location) interface{} {
	pg.entitiesMux.Lock()
	defer pg.entitiesMux.Unlock()
	return pg.unsafeGetObjectByLocation(location)
}

func (pg *Playground) GetObjectByDot(dot engine.Dot) interface{} {
	if v, ok := pg.cMap.Get(dot.Hash()); ok {
		if e, ok := v.(*entity); ok {
			return e.GetObject()
		}
	}
	return nil
}

func (pg *Playground) GetEntityByDot(dot engine.Dot) (interface{}, engine.Location) {
	if v, ok := pg.cMap.Get(dot.Hash()); ok {
		if e, ok := v.(*entity); ok {
			return e.GetObject(), e.GetLocation()
		}
	}
	return nil, nil
}

func (pg *Playground) GetObjectsByDots(dots []engine.Dot) []interface{} {
	if len(dots) == 0 {
		return nil
	}

	objects := make([]interface{}, 0)
	checked := make([]engine.Dot, 0, len(dots))

	for _, dot := range dots {
		flagDotChecked := false

		for i := range checked {
			if checked[i].Equals(dot) {
				flagDotChecked = true
				break
			}
		}

		if flagDotChecked {
			continue
		}

		if v, ok := pg.cMap.Get(dot.Hash()); ok {
			if e, ok := v.(*entity); ok {
				object := e.GetObject()
				flagObjectCreated := false

				for i := range objects {
					if objects[i] == object {
						flagObjectCreated = true
						break
					}
				}

				if !flagObjectCreated {
					objects = append(objects, object)
				}
			}
		}

		checked = append(checked, dot)
	}

	return objects
}

func (pg *Playground) CreateObject(object interface{}, location engine.Location) error {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) CreateObjectAvailableDots(object interface{}, location engine.Location) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) DeleteObject(object interface{}, location engine.Location) error {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) UpdateObject(object interface{}, old, new engine.Location) error {
	// TODO: Implement method.
	return nil
}

func (pg *Playground) UpdateObjectAvailableDots(object interface{}, old, new engine.Location) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomDot(object interface{}) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomRect(object interface{}, rw, rh uint8) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomRectMargin(object interface{}, rw, rh, margin uint8) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) CreateObjectRandomByDotsMask(object interface{}, dm *engine.DotsMask) (engine.Location, error) {
	// TODO: Implement method.
	return nil, nil
}

func (pg *Playground) Navigate(dot engine.Dot, dir engine.Direction, dis uint8) (engine.Dot, error) {
	return pg.area.Navigate(dot, dir, dis)
}

func (pg *Playground) Size() uint16 {
	return pg.area.Size()
}

func (pg *Playground) Width() uint8 {
	return pg.area.Width()
}

func (pg *Playground) Height() uint8 {
	return pg.area.Height()
}

func (pg *Playground) unsafeGetObjects() []interface{} {
	objects := make([]interface{}, len(pg.entities))
	for i, entity := range pg.entities {
		objects[i] = entity.GetObject()
	}
	return objects
}

func (pg *Playground) GetObjects() []interface{} {
	pg.entitiesMux.RLock()
	defer pg.entitiesMux.RUnlock()
	return pg.unsafeGetObjects()
}
<|endoftext|>


<commit_before>package channels

import (
	"log"
	"sync"
	"time"

	"github.com/DanielRenne/GoCore/core/atomicTypes"

	"github.com/DanielRenne/GoCore/core/extensions"
)

//Queue provides a factory to queue channels sequentially and pop / signal them one at a time in a daisy chain.
type Queue struct {
	any      atomicTypes.AtomicBool
	channels sync.Map
}

//Signal will only signal the first item in the queue.
func (q *Queue) Signal(x interface{}) {

	defer func() {
		if r := recover(); r != nil {
			log.Println("Panic Recovered at channels.Signal():  ", r)
			return
		}
	}()

	anyMoreInRange := false
	signaled := false
	q.channels.Range(func(key interface{}, value interface{}) bool {
		if signaled {
			anyMoreInRange = true
			return false
		}

		signaled = true
		c := value.(chan interface{})
		go func() {
			q.channels.Delete(key)
		}()
		c <- x
		return true
	})
	q.any.Set(anyMoreInRange)
}

//Any will return true if there are any current channels waiting.
func (q *Queue) Any() (any bool) {
	any = q.any.Get()
	return
}

//Wait will return a channel for your function to wait on.
func (q *Queue) Wait(x interface{}) (c chan interface{}, any bool) {
	any = q.any.Get()
	q.any.Set(true)
	c = make(chan interface{})
	randomValue := extensions.Random(0, 15)
	q.channels.Store(randomValue, c)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Println("Panic Recovered at channels.Wait->Timeout():  ", r)
				return
			}
		}()
		time.Sleep(time.Millisecond * 5000)
		_, ok := q.channels.Load(randomValue)
		if ok {
			q.channels.Delete(randomValue)
			c <- x
		}

	}()
	return
}
<commit_msg>Changed Timeout to queues to 10000<commit_after>package channels

import (
	"log"
	"sync"
	"time"

	"github.com/DanielRenne/GoCore/core/atomicTypes"

	"github.com/DanielRenne/GoCore/core/extensions"
)

//Queue provides a factory to queue channels sequentially and pop / signal them one at a time in a daisy chain.
type Queue struct {
	any      atomicTypes.AtomicBool
	channels sync.Map
}

//Signal will only signal the first item in the queue.
func (q *Queue) Signal(x interface{}) {

	defer func() {
		if r := recover(); r != nil {
			log.Println("Panic Recovered at channels.Signal():  ", r)
			return
		}
	}()

	anyMoreInRange := false
	signaled := false
	q.channels.Range(func(key interface{}, value interface{}) bool {
		if signaled {
			anyMoreInRange = true
			return false
		}

		signaled = true
		c := value.(chan interface{})
		go func() {
			q.channels.Delete(key)
		}()
		c <- x
		return true
	})
	q.any.Set(anyMoreInRange)
}

//Any will return true if there are any current channels waiting.
func (q *Queue) Any() (any bool) {
	any = q.any.Get()
	return
}

//Wait will return a channel for your function to wait on.
func (q *Queue) Wait(x interface{}) (c chan interface{}, any bool) {
	any = q.any.Get()
	q.any.Set(true)
	c = make(chan interface{})
	randomValue := extensions.Random(0, 15)
	q.channels.Store(randomValue, c)

	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Println("Panic Recovered at channels.Wait->Timeout():  ", r)
				return
			}
		}()
		time.Sleep(time.Millisecond * 10000)
		_, ok := q.channels.Load(randomValue)
		if ok {
			q.channels.Delete(randomValue)
			c <- x
		}

	}()
	return
}
<|endoftext|>


<commit_before>package juggler_test

import (
	"flag"
	"math/rand"
	"net/http/httptest"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/PuerkitoBio/exp/juggler"
	"github.com/PuerkitoBio/exp/juggler/broker/redisbroker"
	"github.com/PuerkitoBio/exp/juggler/callee"
	"github.com/PuerkitoBio/exp/juggler/internal/jugglertest"
	"github.com/PuerkitoBio/exp/juggler/internal/redistest"
	"github.com/PuerkitoBio/exp/juggler/msg"
	"github.com/gorilla/websocket"
	"github.com/stretchr/testify/require"
)

var (
	// redis pool
	redisPoolMaxActiveFlag   = flag.Int("intg.redis-pool-max-active", 250, "redis: maximum active connections in the pool")
	redisPoolMaxIdleFlag     = flag.Int("intg.redis-pool-max-idle", 10, "redis: maximum idle connections in the pool")
	redisPoolIdleTimeoutFlag = flag.Duration("intg.redis-pool-idle-timeout", time.Minute, "redis: idle connection timeout")

	// broker configuration
	brokerBlockingTimeoutFlag = flag.Duration("intg.broker-blocking-timeout", 0, "broker: blocking timeout")
	brokerCallCapFlag         = flag.Int("intg.broker-call-cap", 0, "broker: call requests queue capacity")
	brokerResultCapFlag       = flag.Int("intg.broker-result-cap", 0, "broker: results queue capacity")

	// server configuration
	serverReadLimitFlag               = flag.Int64("intg.server-read-limit", 0, "server: read limit in bytes")
	serverReadTimeoutFlag             = flag.Duration("intg.server-read-timeout", 0, "server: read timeout")
	serverWriteLimitFlag              = flag.Int64("intg.server-write-limit", 0, "server: write limit in bytes")
	serverWriteTimeoutFlag            = flag.Duration("intg.server-write-timeout", 0, "server: write timeout")
	serverAcquireWriteLockTimeoutFlag = flag.Duration("intg.server-acquire-write-lock-timeout", time.Second, "server: acquire write lock timeout")

	nCalleesFlag          = flag.Int("intg.ncallees", 10, "number of registered callees")
	nWorkersPerCalleeFlag = flag.Int("intg.nworkers-per-callee", 10, "number of workers per callee")
	nClientsFlag          = flag.Int("intg.nclients", 100, "number of clients")

	nURIsFlag         = flag.Int("intg.nuris", 10, "number of URIs")
	nChannelsFlag     = flag.Int("intg.nchannels", 10, "number of pub-sub channels")
	durationFlag      = flag.Duration("intg.duration", 10*time.Second, "duration of the integration test")
	clientMsgRateFlag = flag.Duration("intg.client-msg-rate", time.Second, "rate to send client message")
	serverPubRateFlag = flag.Duration("intg.server-pub-rate", time.Second, "rate to send server publish message")
	thunkDelayFlag    = flag.Duration("intg.thunk-delay", 100*time.Millisecond, "delay of a call")
)

func getIntgConfig() *IntgConfig {
	return &IntgConfig{
		RedisPoolMaxActive:   *redisPoolMaxActiveFlag,
		RedisPoolMaxIdle:     *redisPoolMaxIdleFlag,
		RedisPoolIdleTimeout: *redisPoolIdleTimeoutFlag,

		BrokerBlockingTimeout: *brokerBlockingTimeoutFlag,
		BrokerCallCap:         *brokerCallCapFlag,
		BrokerResultCap:       *brokerResultCapFlag,

		ServerReadLimit:               *serverReadLimitFlag,
		ServerReadTimeout:             *serverReadTimeoutFlag,
		ServerWriteLimit:              *serverWriteLimitFlag,
		ServerWriteTimeout:            *serverWriteTimeoutFlag,
		ServerAcquireWriteLockTimeout: *serverAcquireWriteLockTimeoutFlag,

		NCallees:          *nCalleesFlag,
		NWorkersPerCallee: *nWorkersPerCalleeFlag,
		NClients:          *nClientsFlag,

		NURIs:         *nURIsFlag,
		NChannels:     *nChannelsFlag,
		Duration:      *durationFlag,
		ClientMsgRate: *clientMsgRateFlag,
		ServerPubRate: *serverPubRateFlag,
		ThunkDelay:    *thunkDelayFlag,
	}
}

// IntgConfig holds the configuration of an integration test execution.
type IntgConfig struct {
	RedisPoolMaxActive   int
	RedisPoolMaxIdle     int
	RedisPoolIdleTimeout time.Duration

	BrokerBlockingTimeout time.Duration
	BrokerCallCap         int
	BrokerResultCap       int

	ServerReadLimit               int64
	ServerReadTimeout             time.Duration
	ServerWriteLimit              int64
	ServerWriteTimeout            time.Duration
	ServerAcquireWriteLockTimeout time.Duration

	NCallees          int // number of callees listening on the URIs
	NWorkersPerCallee int // number of workers per callee
	NClients          int // number of clients generating calls

	NURIs         int           // number of different URIs to pick from at random
	NChannels     int           // number of different channels to pick from at random
	Duration      time.Duration // duration of the test
	ClientMsgRate time.Duration // send a message at this rate
	ServerPubRate time.Duration // publish a server-side event at this rate
	ThunkDelay    time.Duration // artificial delay of the call
}

func (conf *IntgConfig) URIs() []string {
	uris := make([]string, conf.NURIs)
	for i := 0; i < conf.NURIs; i++ {
		uris[i] = strconv.Itoa(i)
	}
	return uris
}

type runStats struct {
	Call    int64
	Pub     int64
	Sub     int64
	Unsb    int64
	Exp     int64
	OK      int64
	Err     int64
	Res     int64
	Evnt    int64
	Unknown int64
}

func TestIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("integration tests don't run with the -short flag")
	}
	runIntegrationTest(t, &IntgConfig{}) // TODO : parse flags into IntgConfig
}

func incStats(stats *runStats, m msg.Msg) {
	switch m.Type() {
	case msg.CallMsg:
		atomic.AddInt64(&stats.Call, 1)
	case msg.PubMsg:
		atomic.AddInt64(&stats.Pub, 1)
	case msg.SubMsg:
		atomic.AddInt64(&stats.Sub, 1)
	case msg.UnsbMsg:
		atomic.AddInt64(&stats.Unsb, 1)
	case msg.ExpMsg:
		atomic.AddInt64(&stats.Exp, 1)
	case msg.OKMsg:
		atomic.AddInt64(&stats.OK, 1)
	case msg.ErrMsg:
		atomic.AddInt64(&stats.Err, 1)
	case msg.ResMsg:
		atomic.AddInt64(&stats.Res, 1)
	case msg.EvntMsg:
		atomic.AddInt64(&stats.Evnt, 1)
	default:
		atomic.AddInt64(&stats.Unknown, 1)
	}
}

func serverHandler(stats *runStats) juggler.Handler {
	return juggler.HandlerFunc(func(ctx context.Context, c *juggler.Conn, m msg.Msg) {
		incStats(stats, m)
		juggler.ProcessMsg(ctx, c, m)
	})
}

func clientHandler(stats *runStats) juggler.ClientHandler {
	return juggler.ClientHandlerFunc(func(ctx context.Context, c *juggler.Client, m msg.Msg) {
		incStats(stats, m)
	})
}

type runConfig struct {
	conf *IntgConfig

	randSeed      int64
	msgsPerClient [][]msg.Msg
	serverPubs    []msg.Msg
	expected      [2]*runStats // [0]: server, [1]: clients
}

func prepareExec(conf *IntgConfig) *runConfig {
	seed := time.Now().UnixNano()
	rnd := rand.New(rand.NewSource(seed))

	//var srvStats, cliStats runStats

	nCliMsgs := int(conf.Duration / conf.ClientMsgRate)
	mpc := make([][]msg.Msg, conf.NClients)
	for i := 0; i < conf.NClients; i++ {
		mpc[i] = make([]msg.Msg, nCliMsgs)
		for j := 0; j < nCliMsgs; j++ {
			mpc[i][j] = newClientMsg(rnd)
		}
	}

	nSrvMsgs := int(conf.Duration / conf.ServerPubRate)
	sps := make([]msg.Msg, nSrvMsgs)
	for i := 0; i < nSrvMsgs; i++ {
		sps[i] = newServerMsg(rnd)
	}

	return &runConfig{
		conf:          conf,
		randSeed:      seed,
		msgsPerClient: mpc,
		serverPubs:    sps,
	}
}

func newServerMsg(rnd *rand.Rand) msg.Msg {
	return nil
}

func newClientMsg(rnd *rand.Rand) msg.Msg {
	return nil
}

func runIntegrationTest(t *testing.T, conf *IntgConfig) {
	dbgl := &jugglertest.DebugLog{T: t}

	// start/create:
	// 1. redis-server
	// 2. redis pool and broker
	// 3. juggler server
	// 4. m callees
	// 5. n clients

	// 1. redis-server
	cmd, port := redistest.StartServer(t, nil)
	defer cmd.Process.Kill()

	// 2. create the redis pool and broker
	pool := redistest.NewPool(t, ":"+port)
	pool.MaxActive = conf.RedisPoolMaxActive
	pool.MaxIdle = conf.RedisPoolMaxIdle
	pool.IdleTimeout = conf.RedisPoolIdleTimeout
	brk := &redisbroker.Broker{
		Pool:    pool,
		Dial:    pool.Dial,
		LogFunc: dbgl.Printf,

		BlockingTimeout: conf.BrokerBlockingTimeout,
		CallCap:         conf.BrokerCallCap,
		ResultCap:       conf.BrokerResultCap,
	}

	// 3. create the juggler server
	var srvStats runStats
	srv := &juggler.Server{
		CallerBroker: brk,
		PubSubBroker: brk,
		LogFunc:      dbgl.Printf,
		Handler:      serverHandler(&srvStats),

		ReadLimit:               conf.ServerReadLimit,
		ReadTimeout:             conf.ServerReadTimeout,
		WriteLimit:              conf.ServerWriteLimit,
		WriteTimeout:            conf.ServerWriteTimeout,
		AcquireWriteLockTimeout: conf.ServerAcquireWriteLockTimeout,
	}
	upg := &websocket.Upgrader{Subprotocols: juggler.Subprotocols}
	httpsrv := httptest.NewServer(juggler.Upgrade(upg, srv))
	defer httpsrv.Close()

	uris := conf.URIs()
	rc := prepareExec(conf)
	thunk := func(cp *msg.CallPayload) (interface{}, error) {
		time.Sleep(conf.ThunkDelay)
		return "ok", nil
	}

	// 4. start m callees
	calleeStarted := make(chan struct{})
	for i := 0; i < conf.NCallees; i++ {
		go func() {
			cle := callee.Callee{
				Broker:  brk,
				LogFunc: dbgl.Printf,
			}

			conn, err := brk.Calls(uris...)
			if err != nil {
				t.Fatalf("failed to get CallsConn: %v", err)
			}
			defer conn.Close()
			ch := conn.Calls()

			for j := 0; j < conf.NWorkersPerCallee; j++ {
				go func() {
					calleeStarted <- struct{}{}
					for cp := range ch {
						if err := cle.InvokeAndStoreResult(cp, thunk); err != nil {
							t.Fatalf("InvokeAndStoreResult failed: %v", err)
						}
					}
				}()
			}
		}()
	}

	// 5. start n clients
	var clientStats runStats
	clientStarted := make(chan struct{})
	wg := sync.WaitGroup{}
	wg.Add(conf.NClients)

	for i := 0; i < conf.NClients; i++ {
		go func(i int) {
			defer wg.Done()

			cli, err := juggler.Dial(&websocket.Dialer{}, strings.Replace(httpsrv.URL, "http:", "ws:", 1), nil,
				juggler.SetHandler(clientHandler(&clientStats)))
			if err != nil {
				t.Fatalf("Dial failed: %v", err)
			}

			clientStarted <- struct{}{}
			for _, m := range rc.msgsPerClient[i] {
				_ = m
			}
			require.NoError(t, cli.Close(), "Close client %d", i)
		}(i)
	}

	// wait for callees to come online
	for i, cnt := 0, conf.NCallees*conf.NWorkersPerCallee; i < cnt; i++ {
		<-calleeStarted
	}
	// start clients
	for i := 0; i < conf.NClients; i++ {
		<-clientStarted
	}
	// wait for completion
	wg.Wait()
}
<commit_msg>juggler: create messages for integration test<commit_after>package juggler_test

import (
	"flag"
	"math/rand"
	"net/http/httptest"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/PuerkitoBio/exp/juggler"
	"github.com/PuerkitoBio/exp/juggler/broker/redisbroker"
	"github.com/PuerkitoBio/exp/juggler/callee"
	"github.com/PuerkitoBio/exp/juggler/internal/jugglertest"
	"github.com/PuerkitoBio/exp/juggler/internal/redistest"
	"github.com/PuerkitoBio/exp/juggler/msg"
	"github.com/gorilla/websocket"
	"github.com/stretchr/testify/require"
)

var (
	// redis pool
	redisPoolMaxActiveFlag   = flag.Int("intg.redis-pool-max-active", 250, "redis: maximum active connections in the pool")
	redisPoolMaxIdleFlag     = flag.Int("intg.redis-pool-max-idle", 10, "redis: maximum idle connections in the pool")
	redisPoolIdleTimeoutFlag = flag.Duration("intg.redis-pool-idle-timeout", time.Minute, "redis: idle connection timeout")

	// broker configuration
	brokerBlockingTimeoutFlag = flag.Duration("intg.broker-blocking-timeout", 0, "broker: blocking timeout")
	brokerCallCapFlag         = flag.Int("intg.broker-call-cap", 0, "broker: call requests queue capacity")
	brokerResultCapFlag       = flag.Int("intg.broker-result-cap", 0, "broker: results queue capacity")

	// server configuration
	serverReadLimitFlag               = flag.Int64("intg.server-read-limit", 0, "server: read limit in bytes")
	serverReadTimeoutFlag             = flag.Duration("intg.server-read-timeout", 0, "server: read timeout")
	serverWriteLimitFlag              = flag.Int64("intg.server-write-limit", 0, "server: write limit in bytes")
	serverWriteTimeoutFlag            = flag.Duration("intg.server-write-timeout", 0, "server: write timeout")
	serverAcquireWriteLockTimeoutFlag = flag.Duration("intg.server-acquire-write-lock-timeout", time.Second, "server: acquire write lock timeout")

	nCalleesFlag          = flag.Int("intg.ncallees", 10, "number of registered callees")
	nWorkersPerCalleeFlag = flag.Int("intg.nworkers-per-callee", 10, "number of workers per callee")
	nClientsFlag          = flag.Int("intg.nclients", 100, "number of clients")

	nURIsFlag          = flag.Int("intg.nuris", 10, "number of URIs")
	nChannelsFlag      = flag.Int("intg.nchannels", 10, "number of pub-sub channels")
	durationFlag       = flag.Duration("intg.duration", 10*time.Second, "duration of the integration test")
	clientMsgRateFlag  = flag.Duration("intg.client-msg-rate", time.Second, "rate to send client message")
	serverPubRateFlag  = flag.Duration("intg.server-pub-rate", time.Second, "rate to send server publish message")
	thunkDelayFlag     = flag.Duration("intg.thunk-delay", 100*time.Millisecond, "delay of a call")
	callExpireOddsFlag = flag.Int("intg.call-expire-odds", 10, "one chance out of that value for a call to expire")
)

func getIntgConfig() *IntgConfig {
	return &IntgConfig{
		RedisPoolMaxActive:   *redisPoolMaxActiveFlag,
		RedisPoolMaxIdle:     *redisPoolMaxIdleFlag,
		RedisPoolIdleTimeout: *redisPoolIdleTimeoutFlag,

		BrokerBlockingTimeout: *brokerBlockingTimeoutFlag,
		BrokerCallCap:         *brokerCallCapFlag,
		BrokerResultCap:       *brokerResultCapFlag,

		ServerReadLimit:               *serverReadLimitFlag,
		ServerReadTimeout:             *serverReadTimeoutFlag,
		ServerWriteLimit:              *serverWriteLimitFlag,
		ServerWriteTimeout:            *serverWriteTimeoutFlag,
		ServerAcquireWriteLockTimeout: *serverAcquireWriteLockTimeoutFlag,

		NCallees:          *nCalleesFlag,
		NWorkersPerCallee: *nWorkersPerCalleeFlag,
		NClients:          *nClientsFlag,

		NURIs:          *nURIsFlag,
		NChannels:      *nChannelsFlag,
		Duration:       *durationFlag,
		ClientMsgRate:  *clientMsgRateFlag,
		ServerPubRate:  *serverPubRateFlag,
		ThunkDelay:     *thunkDelayFlag,
		CallExpireOdds: *callExpireOddsFlag,
	}
}

// IntgConfig holds the configuration of an integration test execution.
type IntgConfig struct {
	RedisPoolMaxActive   int
	RedisPoolMaxIdle     int
	RedisPoolIdleTimeout time.Duration

	BrokerBlockingTimeout time.Duration
	BrokerCallCap         int
	BrokerResultCap       int

	ServerReadLimit               int64
	ServerReadTimeout             time.Duration
	ServerWriteLimit              int64
	ServerWriteTimeout            time.Duration
	ServerAcquireWriteLockTimeout time.Duration

	NCallees          int // number of callees listening on the URIs
	NWorkersPerCallee int // number of workers per callee
	NClients          int // number of clients generating calls

	NURIs          int           // number of different URIs to pick from at random
	NChannels      int           // number of different channels to pick from at random
	Duration       time.Duration // duration of the test
	ClientMsgRate  time.Duration // send a message at this rate
	ServerPubRate  time.Duration // publish a server-side event at this rate
	ThunkDelay     time.Duration // artificial delay of the call
	CallExpireOdds int           // one chance out of N that a call will expire (timeout < ThunkDelay)
}

func (conf *IntgConfig) URIs() []string {
	uris := make([]string, conf.NURIs)
	for i := 0; i < conf.NURIs; i++ {
		uris[i] = strconv.Itoa(i)
	}
	return uris
}

type runStats struct {
	Call    int64
	Pub     int64
	Sub     int64
	Unsb    int64
	Exp     int64
	OK      int64
	Err     int64
	Res     int64
	Evnt    int64
	Unknown int64
}

func TestIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping the integration test when the -short flag is set")
	}
	runIntegrationTest(t, getIntgConfig())
}

func incStats(stats *runStats, m msg.Msg) {
	switch m.Type() {
	case msg.CallMsg:
		atomic.AddInt64(&stats.Call, 1)
	case msg.PubMsg:
		atomic.AddInt64(&stats.Pub, 1)
	case msg.SubMsg:
		atomic.AddInt64(&stats.Sub, 1)
	case msg.UnsbMsg:
		atomic.AddInt64(&stats.Unsb, 1)
	case msg.ExpMsg:
		atomic.AddInt64(&stats.Exp, 1)
	case msg.OKMsg:
		atomic.AddInt64(&stats.OK, 1)
	case msg.ErrMsg:
		atomic.AddInt64(&stats.Err, 1)
	case msg.ResMsg:
		atomic.AddInt64(&stats.Res, 1)
	case msg.EvntMsg:
		atomic.AddInt64(&stats.Evnt, 1)
	default:
		atomic.AddInt64(&stats.Unknown, 1)
	}
}

func serverHandler(stats *runStats) juggler.Handler {
	return juggler.HandlerFunc(func(ctx context.Context, c *juggler.Conn, m msg.Msg) {
		incStats(stats, m)
		juggler.ProcessMsg(ctx, c, m)
	})
}

func clientHandler(stats *runStats) juggler.ClientHandler {
	return juggler.ClientHandlerFunc(func(ctx context.Context, c *juggler.Client, m msg.Msg) {
		incStats(stats, m)
	})
}

type runConfig struct {
	conf *IntgConfig

	randSeed      int64
	msgsPerClient [][]msg.Msg
	serverPubs    []msg.Msg

	start, end time.Time
}

func prepareExec(t *testing.T, conf *IntgConfig) *runConfig {
	seed := time.Now().UnixNano()
	rnd := rand.New(rand.NewSource(seed))

	nCliMsgs := int(conf.Duration / conf.ClientMsgRate)
	mpc := make([][]msg.Msg, conf.NClients)
	for i := 0; i < conf.NClients; i++ {
		mpc[i] = make([]msg.Msg, nCliMsgs)
		for j := 0; j < nCliMsgs; j++ {
			mpc[i][j] = newClientMsg(t, conf, rnd)
		}
	}

	nSrvMsgs := int(conf.Duration / conf.ServerPubRate)
	sps := make([]msg.Msg, nSrvMsgs)
	for i := 0; i < nSrvMsgs; i++ {
		sps[i] = newServerMsg(t, conf, rnd)
	}

	return &runConfig{
		conf:          conf,
		randSeed:      seed,
		msgsPerClient: mpc,
		serverPubs:    sps,
	}
}

func newServerMsg(t *testing.T, conf *IntgConfig, rnd *rand.Rand) msg.Msg {
	ch := strconv.Itoa(rnd.Intn(conf.NChannels))
	pub, err := msg.NewPub(ch, "server event")
	require.NoError(t, err, "NewPub failed")
	return pub
}

func newClientMsg(t *testing.T, conf *IntgConfig, rnd *rand.Rand) msg.Msg {
	var m msg.Msg

	mt := msg.MessageType(rnd.Intn(int(msg.UnsbMsg)) + 1)
	switch mt {
	case msg.CallMsg:
		uri := strconv.Itoa(rnd.Intn(conf.NURIs))
		exp := rnd.Intn(conf.CallExpireOdds)
		to := 10 * conf.ThunkDelay
		if exp == 0 {
			to = time.Millisecond
		}
		call, err := msg.NewCall(uri, "client call", to)
		require.NoError(t, err, "NewCall failed")
		m = call

	case msg.SubMsg:
		ch := strconv.Itoa(rnd.Intn(conf.NChannels))
		m = msg.NewSub(ch, false)

	case msg.UnsbMsg:
		ch := strconv.Itoa(rnd.Intn(conf.NChannels))
		m = msg.NewUnsb(ch, false)

	case msg.PubMsg:
		ch := strconv.Itoa(rnd.Intn(conf.NChannels))
		pub, err := msg.NewPub(ch, "client pub")
		require.NoError(t, err, "NewPub failed")
		m = pub
	}
	return m
}

func runIntegrationTest(t *testing.T, conf *IntgConfig) {
	dbgl := &jugglertest.DebugLog{T: t}

	// start/create:
	// 1. redis-server
	// 2. redis pool and broker
	// 3. juggler server
	// 4. m callees
	// 5. n clients

	// 1. redis-server
	cmd, port := redistest.StartServer(t, nil)
	defer cmd.Process.Kill()

	// 2. create the redis pool and broker
	pool := redistest.NewPool(t, ":"+port)
	pool.MaxActive = conf.RedisPoolMaxActive
	pool.MaxIdle = conf.RedisPoolMaxIdle
	pool.IdleTimeout = conf.RedisPoolIdleTimeout
	brk := &redisbroker.Broker{
		Pool:    pool,
		Dial:    pool.Dial,
		LogFunc: dbgl.Printf,

		BlockingTimeout: conf.BrokerBlockingTimeout,
		CallCap:         conf.BrokerCallCap,
		ResultCap:       conf.BrokerResultCap,
	}

	// 3. create the juggler server
	var srvStats runStats
	srv := &juggler.Server{
		CallerBroker: brk,
		PubSubBroker: brk,
		LogFunc:      dbgl.Printf,
		Handler:      serverHandler(&srvStats),

		ReadLimit:               conf.ServerReadLimit,
		ReadTimeout:             conf.ServerReadTimeout,
		WriteLimit:              conf.ServerWriteLimit,
		WriteTimeout:            conf.ServerWriteTimeout,
		AcquireWriteLockTimeout: conf.ServerAcquireWriteLockTimeout,
	}
	upg := &websocket.Upgrader{Subprotocols: juggler.Subprotocols}
	httpsrv := httptest.NewServer(juggler.Upgrade(upg, srv))
	defer httpsrv.Close()

	uris := conf.URIs()
	rc := prepareExec(t, conf)
	thunk := func(cp *msg.CallPayload) (interface{}, error) {
		time.Sleep(conf.ThunkDelay)
		return "ok", nil
	}

	// 4. start m callees
	calleeStarted := make(chan struct{})
	for i := 0; i < conf.NCallees; i++ {
		go func() {
			cle := callee.Callee{
				Broker:  brk,
				LogFunc: dbgl.Printf,
			}

			conn, err := brk.Calls(uris...)
			if err != nil {
				t.Fatalf("failed to get CallsConn: %v", err)
			}
			defer conn.Close()
			ch := conn.Calls()

			for j := 0; j < conf.NWorkersPerCallee; j++ {
				go func() {
					calleeStarted <- struct{}{}
					for cp := range ch {
						if err := cle.InvokeAndStoreResult(cp, thunk); err != nil {
							t.Fatalf("InvokeAndStoreResult failed: %v", err)
						}
					}
				}()
			}
		}()
	}

	// 5. start n clients
	var clientStats runStats
	clientStarted := make(chan struct{})
	wg := sync.WaitGroup{}
	wg.Add(conf.NClients)

	for i := 0; i < conf.NClients; i++ {
		go func(i int) {
			defer wg.Done()

			cli, err := juggler.Dial(&websocket.Dialer{}, strings.Replace(httpsrv.URL, "http:", "ws:", 1), nil,
				juggler.SetHandler(clientHandler(&clientStats)))
			if err != nil {
				t.Fatalf("Dial failed: %v", err)
			}

			clientStarted <- struct{}{}
			for _, m := range rc.msgsPerClient[i] {
				_ = m
			}
			require.NoError(t, cli.Close(), "Close client %d", i)
		}(i)
	}

	// wait for callees to come online
	for i, cnt := 0, conf.NCallees*conf.NWorkersPerCallee; i < cnt; i++ {
		<-calleeStarted
	}
	// start clients
	rc.start = time.Now()
	for i := 0; i < conf.NClients; i++ {
		<-clientStarted
	}
	// wait for completion
	wg.Wait()
	rc.end = time.Now()

	checkAndPrintResults(t, rc, &srvStats, &clientStats)
}

func checkAndPrintResults(t *testing.T, rc *runConfig, srv, cli *runStats) {

}
<|endoftext|>