<commit_before>// +skip_license_check

/*
This file contains portions of code directly taken from the 'xenolf/lego' project.
A copy of the license for this code can be found in the file named LICENSE in
this directory.
*/

// Package route53 implements a DNS provider for solving the DNS-01 challenge
// using AWS Route 53 DNS.
package route53

import (
	"fmt"
	"strings"
	"time"

	logf "github.com/cert-manager/cert-manager/pkg/logs"

	"github.com/go-logr/logr"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/aws/aws-sdk-go/service/sts/stsiface"
	"github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util"
)

const (
	route53TTL = 10
)

// DNSProvider implements the util.ChallengeProvider interface
type DNSProvider struct {
	dns01Nameservers []string
	client           *route53.Route53
	hostedZoneID     string
	log              logr.Logger

	userAgent string
}

type sessionProvider struct {
	AccessKeyID     string
	SecretAccessKey string
	Ambient         bool
	Region          string
	Role            string
	StsProvider     func(*session.Session) stsiface.STSAPI
	log             logr.Logger
	userAgent       string
}

func (d *sessionProvider) GetSession() (*session.Session, error) {
	if d.AccessKeyID == "" && d.SecretAccessKey == "" {
		if !d.Ambient {
			return nil, fmt.Errorf("unable to construct route53 provider: empty credentials; perhaps you meant to enable ambient credentials?")
		}
	} else if d.AccessKeyID == "" || d.SecretAccessKey == "" {
		// It's always an error to set one of those but not the other
		return nil, fmt.Errorf("unable to construct route53 provider: only one of access and secret key was provided")
	}

	useAmbientCredentials := d.Ambient && (d.AccessKeyID == "" && d.SecretAccessKey == "")

	config := aws.NewConfig()
	sessionOpts := session.Options{
		Config: *config,
	}

	if useAmbientCredentials {
		d.log.V(logf.DebugLevel).Info("using ambient credentials")
		// Leaving credentials unset results in a default credential chain being
		// used; this chain is a reasonable default for getting ambient creds.
		// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
	} else {
		d.log.V(logf.DebugLevel).Info("not using ambient credentials")
		sessionOpts.Config.Credentials = credentials.NewStaticCredentials(d.AccessKeyID, d.SecretAccessKey, "")
		// also disable 'ambient' region sources
		sessionOpts.SharedConfigState = session.SharedConfigDisable
	}

	sess, err := session.NewSessionWithOptions(sessionOpts)
	if err != nil {
		return nil, fmt.Errorf("unable to create aws session: %s", err)
	}

	if d.Role != "" {
		d.log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role")
		stsSvc := d.StsProvider(sess)
		result, err := stsSvc.AssumeRole(&sts.AssumeRoleInput{
			RoleArn:         aws.String(d.Role),
			RoleSessionName: aws.String("cert-manager"),
		})
		if err != nil {
			return nil, fmt.Errorf("unable to assume role: %s", err)
		}

		creds := credentials.Value{
			AccessKeyID:     *result.Credentials.AccessKeyId,
			SecretAccessKey: *result.Credentials.SecretAccessKey,
			SessionToken:    *result.Credentials.SessionToken,
		}
		sessionOpts.Config.Credentials = credentials.NewStaticCredentialsFromCreds(creds)

		sess, err = session.NewSessionWithOptions(sessionOpts)
		if err != nil {
			return nil, fmt.Errorf("unable to create aws session: %s", err)
		}
	}

	// If ambient credentials aren't permitted, always set the region, even if to
	// empty string, to avoid it falling back on the environment.
	// this has to be set after session is constructed
	if d.Region != "" || !useAmbientCredentials {
		sess.Config.WithRegion(d.Region)
	}

	sess.Handlers.Build.PushBack(request.WithAppendUserAgent(d.userAgent))
	return sess, nil
}

func newSessionProvider(accessKeyID, secretAccessKey, region, role string, ambient bool, userAgent string) (*sessionProvider, error) {
	return &sessionProvider{
		AccessKeyID:     accessKeyID,
		SecretAccessKey: secretAccessKey,
		Ambient:         ambient,
		Region:          region,
		Role:            role,
		StsProvider:     defaultSTSProvider,
		log:             logf.Log.WithName("route53-session-provider"),
		userAgent:       userAgent,
	}, nil
}

func defaultSTSProvider(sess *session.Session) stsiface.STSAPI {
	return sts.New(sess)
}

// NewDNSProvider returns a DNSProvider instance configured for the AWS
// Route 53 service using static credentials from its parameters or, if they're
// unset and the 'ambient' option is set, credentials from the environment.
func NewDNSProvider(accessKeyID, secretAccessKey, hostedZoneID, region, role string,
	ambient bool,
	dns01Nameservers []string,
	userAgent string,
) (*DNSProvider, error) {
	provider, err := newSessionProvider(accessKeyID, secretAccessKey, region, role, ambient, userAgent)
	if err != nil {
		return nil, err
	}

	sess, err := provider.GetSession()
	if err != nil {
		return nil, err
	}

	client := route53.New(sess)

	return &DNSProvider{
		client:           client,
		hostedZoneID:     hostedZoneID,
		dns01Nameservers: dns01Nameservers,
		log:              logf.Log.WithName("route53"),
		userAgent:        userAgent,
	}, nil
}

// Present creates a TXT record using the specified parameters
func (r *DNSProvider) Present(domain, fqdn, value string) error {
	value = `"` + value + `"`
	return r.changeRecord(route53.ChangeActionCreate, fqdn, value, route53TTL)
}

// CleanUp removes the TXT record matching the specified parameters
func (r *DNSProvider) CleanUp(domain, fqdn, value string) error {
	value = `"` + value + `"`
	return r.changeRecord(route53.ChangeActionDelete, fqdn, value, route53TTL)
}

func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
	hostedZoneID, err := r.getHostedZoneID(fqdn)
	if err != nil {
		return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err)
	}

	recordSet := newTXTRecordSet(fqdn, value, ttl)
	reqParams := &route53.ChangeResourceRecordSetsInput{
		HostedZoneId: aws.String(hostedZoneID),
		ChangeBatch: &route53.ChangeBatch{
			Comment: aws.String("Managed by cert-manager"),
			Changes: []*route53.Change{
				{
					Action:            &action,
					ResourceRecordSet: recordSet,
				},
			},
		},
	}

	resp, err := r.client.ChangeResourceRecordSets(reqParams)
	if err != nil {
		if awserr, ok := err.(awserr.Error); ok {
			if action == route53.ChangeActionDelete && awserr.Code() == route53.ErrCodeInvalidChangeBatch {
				r.log.V(logf.DebugLevel).WithValues("error", err).Info("ignoring InvalidChangeBatch error")
				// If we try to delete something and get a 'InvalidChangeBatch' that
				// means it's already deleted, no need to consider it an error.
				return nil
			}
		}
		return fmt.Errorf("failed to change Route 53 record set: %v", removeReqID(err))

	}

	statusID := resp.ChangeInfo.Id

	return util.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
		reqParams := &route53.GetChangeInput{
			Id: statusID,
		}
		resp, err := r.client.GetChange(reqParams)
		if err != nil {
			return false, fmt.Errorf("failed to query Route 53 change status: %v", removeReqID(err))
		}
		if *resp.ChangeInfo.Status == route53.ChangeStatusInsync {
			return true, nil
		}
		return false, nil
	})
}

func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
	if r.hostedZoneID != "" {
		return r.hostedZoneID, nil
	}

	authZone, err := util.FindZoneByFqdn(fqdn, r.dns01Nameservers)
	if err != nil {
		return "", fmt.Errorf("error finding zone from fqdn: %v", err)
	}

	// .DNSName should not have a trailing dot
	reqParams := &route53.ListHostedZonesByNameInput{
		DNSName: aws.String(util.UnFqdn(authZone)),
	}
	resp, err := r.client.ListHostedZonesByName(reqParams)
	if err != nil {
		return "", removeReqID(err)
	}

	zoneToID := make(map[string]string)
	var hostedZones []string
	for _, hostedZone := range resp.HostedZones {
		// .Name has a trailing dot
		if !*hostedZone.Config.PrivateZone {
			zoneToID[*hostedZone.Name] = *hostedZone.Id
			hostedZones = append(hostedZones, *hostedZone.Name)
		}
	}
	authZone, err = util.FindBestMatch(fqdn, hostedZones...)
	if err != nil {
		return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn)
	}

	hostedZoneID, ok := zoneToID[authZone]

	if len(hostedZoneID) == 0 || !ok {
		return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn)
	}

	if strings.HasPrefix(hostedZoneID, "/hostedzone/") {
		hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/")
	}

	return hostedZoneID, nil
}

func newTXTRecordSet(fqdn, value string, ttl int) *route53.ResourceRecordSet {
	return &route53.ResourceRecordSet{
		Name:             aws.String(fqdn),
		Type:             aws.String(route53.RRTypeTxt),
		TTL:              aws.Int64(int64(ttl)),
		MultiValueAnswer: aws.Bool(true),
		SetIdentifier:    aws.String(value),
		ResourceRecords: []*route53.ResourceRecord{
			{Value: aws.String(value)},
		},
	}
}

// The aws-sdk-go library appends a request id to its error messages. We
// want our error messages to be the same when the cause is the same to
// avoid spurious challenge updates.
//
// The given error must not be nil. This function must be called everywhere
// we have a non-nil error coming from an aws-sdk-go func.
func removeReqID(err error) error {
	// NOTE(mael): I first tried to unwrap the RequestFailure to get rid of
	// this request id. But the concrete type requestFailure is private, so
	// I can't unwrap it. Instead, I recreate a new awserr.baseError. It's
	// also a awserr.Error except it doesn't have the request id.
	//
	// Also note that we do not give the origErr to awserr.New. If we did,
	// err.Error() would show the origErr, which we don't want since it
	// contains a request id.
	if e, ok := err.(awserr.RequestFailure); ok {
		return awserr.New(e.Code(), e.Message(), nil)
	}
	return err
}
<commit_msg>It seems ther is a need to perfrom upsert instead of a simple create.<commit_after>// +skip_license_check

/*
This file contains portions of code directly taken from the 'xenolf/lego' project.
A copy of the license for this code can be found in the file named LICENSE in
this directory.
*/

// Package route53 implements a DNS provider for solving the DNS-01 challenge
// using AWS Route 53 DNS.
package route53

import (
	"fmt"
	"strings"
	"time"

	logf "github.com/cert-manager/cert-manager/pkg/logs"

	"github.com/go-logr/logr"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/aws/aws-sdk-go/service/sts/stsiface"
	"github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util"
)

const (
	route53TTL = 10
)

// DNSProvider implements the util.ChallengeProvider interface
type DNSProvider struct {
	dns01Nameservers []string
	client           *route53.Route53
	hostedZoneID     string
	log              logr.Logger

	userAgent string
}

type sessionProvider struct {
	AccessKeyID     string
	SecretAccessKey string
	Ambient         bool
	Region          string
	Role            string
	StsProvider     func(*session.Session) stsiface.STSAPI
	log             logr.Logger
	userAgent       string
}

func (d *sessionProvider) GetSession() (*session.Session, error) {
	if d.AccessKeyID == "" && d.SecretAccessKey == "" {
		if !d.Ambient {
			return nil, fmt.Errorf("unable to construct route53 provider: empty credentials; perhaps you meant to enable ambient credentials?")
		}
	} else if d.AccessKeyID == "" || d.SecretAccessKey == "" {
		// It's always an error to set one of those but not the other
		return nil, fmt.Errorf("unable to construct route53 provider: only one of access and secret key was provided")
	}

	useAmbientCredentials := d.Ambient && (d.AccessKeyID == "" && d.SecretAccessKey == "")

	config := aws.NewConfig()
	sessionOpts := session.Options{
		Config: *config,
	}

	if useAmbientCredentials {
		d.log.V(logf.DebugLevel).Info("using ambient credentials")
		// Leaving credentials unset results in a default credential chain being
		// used; this chain is a reasonable default for getting ambient creds.
		// https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials
	} else {
		d.log.V(logf.DebugLevel).Info("not using ambient credentials")
		sessionOpts.Config.Credentials = credentials.NewStaticCredentials(d.AccessKeyID, d.SecretAccessKey, "")
		// also disable 'ambient' region sources
		sessionOpts.SharedConfigState = session.SharedConfigDisable
	}

	sess, err := session.NewSessionWithOptions(sessionOpts)
	if err != nil {
		return nil, fmt.Errorf("unable to create aws session: %s", err)
	}

	if d.Role != "" {
		d.log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role")
		stsSvc := d.StsProvider(sess)
		result, err := stsSvc.AssumeRole(&sts.AssumeRoleInput{
			RoleArn:         aws.String(d.Role),
			RoleSessionName: aws.String("cert-manager"),
		})
		if err != nil {
			return nil, fmt.Errorf("unable to assume role: %s", err)
		}

		creds := credentials.Value{
			AccessKeyID:     *result.Credentials.AccessKeyId,
			SecretAccessKey: *result.Credentials.SecretAccessKey,
			SessionToken:    *result.Credentials.SessionToken,
		}
		sessionOpts.Config.Credentials = credentials.NewStaticCredentialsFromCreds(creds)

		sess, err = session.NewSessionWithOptions(sessionOpts)
		if err != nil {
			return nil, fmt.Errorf("unable to create aws session: %s", err)
		}
	}

	// If ambient credentials aren't permitted, always set the region, even if to
	// empty string, to avoid it falling back on the environment.
	// this has to be set after session is constructed
	if d.Region != "" || !useAmbientCredentials {
		sess.Config.WithRegion(d.Region)
	}

	sess.Handlers.Build.PushBack(request.WithAppendUserAgent(d.userAgent))
	return sess, nil
}

func newSessionProvider(accessKeyID, secretAccessKey, region, role string, ambient bool, userAgent string) (*sessionProvider, error) {
	return &sessionProvider{
		AccessKeyID:     accessKeyID,
		SecretAccessKey: secretAccessKey,
		Ambient:         ambient,
		Region:          region,
		Role:            role,
		StsProvider:     defaultSTSProvider,
		log:             logf.Log.WithName("route53-session-provider"),
		userAgent:       userAgent,
	}, nil
}

func defaultSTSProvider(sess *session.Session) stsiface.STSAPI {
	return sts.New(sess)
}

// NewDNSProvider returns a DNSProvider instance configured for the AWS
// Route 53 service using static credentials from its parameters or, if they're
// unset and the 'ambient' option is set, credentials from the environment.
func NewDNSProvider(accessKeyID, secretAccessKey, hostedZoneID, region, role string,
	ambient bool,
	dns01Nameservers []string,
	userAgent string,
) (*DNSProvider, error) {
	provider, err := newSessionProvider(accessKeyID, secretAccessKey, region, role, ambient, userAgent)
	if err != nil {
		return nil, err
	}

	sess, err := provider.GetSession()
	if err != nil {
		return nil, err
	}

	client := route53.New(sess)

	return &DNSProvider{
		client:           client,
		hostedZoneID:     hostedZoneID,
		dns01Nameservers: dns01Nameservers,
		log:              logf.Log.WithName("route53"),
		userAgent:        userAgent,
	}, nil
}

// Present creates a TXT record using the specified parameters
func (r *DNSProvider) Present(domain, fqdn, value string) error {
	value = `"` + value + `"`
	return r.changeRecord(route53.ChangeActionUpsert, fqdn, value, route53TTL)
}

// CleanUp removes the TXT record matching the specified parameters
func (r *DNSProvider) CleanUp(domain, fqdn, value string) error {
	value = `"` + value + `"`
	return r.changeRecord(route53.ChangeActionDelete, fqdn, value, route53TTL)
}

func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
	hostedZoneID, err := r.getHostedZoneID(fqdn)
	if err != nil {
		return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err)
	}

	recordSet := newTXTRecordSet(fqdn, value, ttl)
	reqParams := &route53.ChangeResourceRecordSetsInput{
		HostedZoneId: aws.String(hostedZoneID),
		ChangeBatch: &route53.ChangeBatch{
			Comment: aws.String("Managed by cert-manager"),
			Changes: []*route53.Change{
				{
					Action:            &action,
					ResourceRecordSet: recordSet,
				},
			},
		},
	}

	resp, err := r.client.ChangeResourceRecordSets(reqParams)
	if err != nil {
		if awserr, ok := err.(awserr.Error); ok {
			if action == route53.ChangeActionDelete && awserr.Code() == route53.ErrCodeInvalidChangeBatch {
				r.log.V(logf.DebugLevel).WithValues("error", err).Info("ignoring InvalidChangeBatch error")
				// If we try to delete something and get a 'InvalidChangeBatch' that
				// means it's already deleted, no need to consider it an error.
				return nil
			}
		}
		return fmt.Errorf("failed to change Route 53 record set: %v", removeReqID(err))

	}

	statusID := resp.ChangeInfo.Id

	return util.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
		reqParams := &route53.GetChangeInput{
			Id: statusID,
		}
		resp, err := r.client.GetChange(reqParams)
		if err != nil {
			return false, fmt.Errorf("failed to query Route 53 change status: %v", removeReqID(err))
		}
		if *resp.ChangeInfo.Status == route53.ChangeStatusInsync {
			return true, nil
		}
		return false, nil
	})
}

func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
	if r.hostedZoneID != "" {
		return r.hostedZoneID, nil
	}

	authZone, err := util.FindZoneByFqdn(fqdn, r.dns01Nameservers)
	if err != nil {
		return "", fmt.Errorf("error finding zone from fqdn: %v", err)
	}

	// .DNSName should not have a trailing dot
	reqParams := &route53.ListHostedZonesByNameInput{
		DNSName: aws.String(util.UnFqdn(authZone)),
	}
	resp, err := r.client.ListHostedZonesByName(reqParams)
	if err != nil {
		return "", removeReqID(err)
	}

	zoneToID := make(map[string]string)
	var hostedZones []string
	for _, hostedZone := range resp.HostedZones {
		// .Name has a trailing dot
		if !*hostedZone.Config.PrivateZone {
			zoneToID[*hostedZone.Name] = *hostedZone.Id
			hostedZones = append(hostedZones, *hostedZone.Name)
		}
	}
	authZone, err = util.FindBestMatch(fqdn, hostedZones...)
	if err != nil {
		return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn)
	}

	hostedZoneID, ok := zoneToID[authZone]

	if len(hostedZoneID) == 0 || !ok {
		return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn)
	}

	if strings.HasPrefix(hostedZoneID, "/hostedzone/") {
		hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/")
	}

	return hostedZoneID, nil
}

func newTXTRecordSet(fqdn, value string, ttl int) *route53.ResourceRecordSet {
	return &route53.ResourceRecordSet{
		Name:             aws.String(fqdn),
		Type:             aws.String(route53.RRTypeTxt),
		TTL:              aws.Int64(int64(ttl)),
		MultiValueAnswer: aws.Bool(true),
		SetIdentifier:    aws.String(value),
		ResourceRecords: []*route53.ResourceRecord{
			{Value: aws.String(value)},
		},
	}
}

// The aws-sdk-go library appends a request id to its error messages. We
// want our error messages to be the same when the cause is the same to
// avoid spurious challenge updates.
//
// The given error must not be nil. This function must be called everywhere
// we have a non-nil error coming from an aws-sdk-go func.
func removeReqID(err error) error {
	// NOTE(mael): I first tried to unwrap the RequestFailure to get rid of
	// this request id. But the concrete type requestFailure is private, so
	// I can't unwrap it. Instead, I recreate a new awserr.baseError. It's
	// also a awserr.Error except it doesn't have the request id.
	//
	// Also note that we do not give the origErr to awserr.New. If we did,
	// err.Error() would show the origErr, which we don't want since it
	// contains a request id.
	if e, ok := err.(awserr.RequestFailure); ok {
		return awserr.New(e.Code(), e.Message(), nil)
	}
	return err
}
<|endoftext|>


<commit_before>package sqlstore

import (
	"github.com/grafana/grafana/pkg/bus"
	m "github.com/grafana/grafana/pkg/models"
)

func init() {
	bus.AddHandler("sql", UpdateDashboardAcl)
	bus.AddHandler("sql", GetDashboardAclInfoList)
}

func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
	return inTransaction(func(sess *DBSession) error {
		// delete existing items
		_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", cmd.DashboardId)
		if err != nil {
			return err
		}

		for _, item := range cmd.Items {
			if item.UserId == 0 && item.TeamId == 0 && (item.Role == nil || !item.Role.IsValid()) {
				return m.ErrDashboardAclInfoMissing
			}

			if item.DashboardId == 0 {
				return m.ErrDashboardPermissionDashboardEmpty
			}

			sess.Nullable("user_id", "team_id")
			if _, err := sess.Insert(item); err != nil {
				return err
			}
		}

		// Update dashboard HasAcl flag
		dashboard := m.Dashboard{HasAcl: true}
		_, err = sess.Cols("has_acl").Where("id=?", cmd.DashboardId).Update(&dashboard)
		return err
	})
}

// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
// different places.
// 1) Permissions for the dashboard
// 2) permissions for its parent folder
// 3) if no specific permissions have been set for the dashboard or its parent folder then get the default permissions
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
	var err error

	falseStr := dialect.BooleanStr(false)

	if query.DashboardId == 0 {
		sql := `SELECT
		da.id,
		da.org_id,
		da.dashboard_id,
		da.user_id,
		da.team_id,
		da.permission,
		da.role,
		da.created,
		da.updated,
		'' as user_login,
		'' as user_email,
		'' as team,
		'' as title,
		'' as slug,
		'' as uid,` +
			falseStr + ` AS is_folder,` +
			falseStr + ` AS inherited
		FROM dashboard_acl as da
		WHERE da.dashboard_id = -1`
		query.Result = make([]*m.DashboardAclInfoDTO, 0)
		err = x.SQL(sql).Find(&query.Result)

	} else {

		rawSQL := `
			-- get permissions for the dashboard and its parent folder
			SELECT
				da.id,
				da.org_id,
				da.dashboard_id,
				da.user_id,
				da.team_id,
				da.permission,
				da.role,
				da.created,
				da.updated,
				u.login AS user_login,
				u.email AS user_email,
				ug.name AS team,
				ug.email AS team_email,
				d.title,
				d.slug,
				d.uid,
				d.is_folder,
				CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
			FROM dashboard as d
				LEFT JOIN dashboard folder on folder.id = d.folder_id
				LEFT JOIN dashboard_acl AS da ON
				da.dashboard_id = d.id OR
				da.dashboard_id = d.folder_id OR
				(
					-- include default permissions -->
					da.org_id = -1 AND (
					  (folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
					  (folder.id IS NULL AND d.has_acl = ` + falseStr + `)
					)
				)
				LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
				LEFT JOIN team ug on ug.id = da.team_id
			WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
			ORDER BY 1 ASC
			`

		query.Result = make([]*m.DashboardAclInfoDTO, 0)
		err = x.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
	}

	for _, p := range query.Result {
		p.PermissionName = p.Permission.String()
	}

	return err
}
<commit_msg>sqlstore: use column name in order by (#16583)<commit_after>package sqlstore

import (
	"github.com/grafana/grafana/pkg/bus"
	m "github.com/grafana/grafana/pkg/models"
)

func init() {
	bus.AddHandler("sql", UpdateDashboardAcl)
	bus.AddHandler("sql", GetDashboardAclInfoList)
}

func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
	return inTransaction(func(sess *DBSession) error {
		// delete existing items
		_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", cmd.DashboardId)
		if err != nil {
			return err
		}

		for _, item := range cmd.Items {
			if item.UserId == 0 && item.TeamId == 0 && (item.Role == nil || !item.Role.IsValid()) {
				return m.ErrDashboardAclInfoMissing
			}

			if item.DashboardId == 0 {
				return m.ErrDashboardPermissionDashboardEmpty
			}

			sess.Nullable("user_id", "team_id")
			if _, err := sess.Insert(item); err != nil {
				return err
			}
		}

		// Update dashboard HasAcl flag
		dashboard := m.Dashboard{HasAcl: true}
		_, err = sess.Cols("has_acl").Where("id=?", cmd.DashboardId).Update(&dashboard)
		return err
	})
}

// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
// different places.
// 1) Permissions for the dashboard
// 2) permissions for its parent folder
// 3) if no specific permissions have been set for the dashboard or its parent folder then get the default permissions
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
	var err error

	falseStr := dialect.BooleanStr(false)

	if query.DashboardId == 0 {
		sql := `SELECT
		da.id,
		da.org_id,
		da.dashboard_id,
		da.user_id,
		da.team_id,
		da.permission,
		da.role,
		da.created,
		da.updated,
		'' as user_login,
		'' as user_email,
		'' as team,
		'' as title,
		'' as slug,
		'' as uid,` +
			falseStr + ` AS is_folder,` +
			falseStr + ` AS inherited
		FROM dashboard_acl as da
		WHERE da.dashboard_id = -1`
		query.Result = make([]*m.DashboardAclInfoDTO, 0)
		err = x.SQL(sql).Find(&query.Result)

	} else {

		rawSQL := `
			-- get permissions for the dashboard and its parent folder
			SELECT
				da.id,
				da.org_id,
				da.dashboard_id,
				da.user_id,
				da.team_id,
				da.permission,
				da.role,
				da.created,
				da.updated,
				u.login AS user_login,
				u.email AS user_email,
				ug.name AS team,
				ug.email AS team_email,
				d.title,
				d.slug,
				d.uid,
				d.is_folder,
				CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
			FROM dashboard as d
				LEFT JOIN dashboard folder on folder.id = d.folder_id
				LEFT JOIN dashboard_acl AS da ON
				da.dashboard_id = d.id OR
				da.dashboard_id = d.folder_id OR
				(
					-- include default permissions -->
					da.org_id = -1 AND (
					  (folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
					  (folder.id IS NULL AND d.has_acl = ` + falseStr + `)
					)
				)
				LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
				LEFT JOIN team ug on ug.id = da.team_id
			WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
			ORDER BY da.id ASC
			`

		query.Result = make([]*m.DashboardAclInfoDTO, 0)
		err = x.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
	}

	for _, p := range query.Result {
		p.PermissionName = p.Permission.String()
	}

	return err
}
<|endoftext|>


<commit_before>package fuse

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	log "github.com/Sirupsen/logrus"
	"github.com/sahib/brig/catfs"
	"github.com/sahib/brig/util/testutil"
	"github.com/stretchr/testify/require"
)

func init() {
	// NOTE: This is useful for debugging.
	log.SetLevel(log.DebugLevel)
}

func withDummyFS(t *testing.T, fn func(fs *catfs.FS)) {
	backend := catfs.NewMemFsBackend()
	owner := "alice"

	dbPath, err := ioutil.TempDir("", "brig-fs-test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}

	defer os.RemoveAll(dbPath)

	fs, err := catfs.NewFilesystem(backend, dbPath, owner, false, nil)
	if err != nil {
		t.Fatalf("Failed to create filesystem: %v", err)
	}

	fn(fs)

	if err := fs.Close(); err != nil {
		t.Fatalf("Failed to close filesystem: %v", err)
	}
}

func withMount(t *testing.T, f func(mount *Mount)) {
	mntPath := filepath.Join(os.TempDir(), "brig-fuse-mountdir")

	if err := os.MkdirAll(mntPath, 0777); err != nil {
		t.Fatalf("Unable to create empty mount dir: %v", err)
	}

	defer testutil.Remover(t, mntPath)

	withDummyFS(t, func(fs *catfs.FS) {
		mount, err := NewMount(fs, mntPath)
		if err != nil {
			t.Fatalf("Cannot create mount: %v", err)
		}

		f(mount)

		if err := mount.Close(); err != nil {
			t.Fatalf("Closing mount failed: %v", err)
		}
	})
}

func checkForCorrectFile(t *testing.T, path string, data []byte) {
	// Try to read it over fuse:
	helloBuffer := &bytes.Buffer{}
	fd, err := os.Open(path)
	if err != nil {
		t.Fatalf("Unable to open simple file over fuse: %v", err)
	}

	defer func() {
		if err := fd.Close(); err != nil {
			t.Fatalf("Unable to close simple file over fuse: %v", err)
		}
	}()

	n, err := io.CopyBuffer(helloBuffer, fd, make([]byte, 128*1024))
	if err != nil {
		t.Fatalf("Unable to read full simple file over fuse: %v", err)
	}

	if n != int64(len(data)) {
		t.Fatalf("Data differs over fuse: got %d, should be %d bytes", n, len(data))
	}

	if !bytes.Equal(helloBuffer.Bytes(), data) {
		t.Errorf(
			"Data from simple file does not match source. Len: %d %d",
			len(data),
			helloBuffer.Len(),
		)

		require.Equal(t, data, helloBuffer.Bytes())
	}
}

var (
	DataSizes = []int64{
		0, 1, 2, 4, 8, 16, 32, 64, 1024,
		2048, 4095, 4096, 4097, 147611,
	}
)

func TestRead(t *testing.T) {
	withMount(t, func(mount *Mount) {
		for _, size := range DataSizes {
			t.Run(fmt.Sprintf("%d", size), func(t *testing.T) {
				helloData := testutil.CreateDummyBuf(size)

				// Add a simple file:
				name := fmt.Sprintf("hello_%d", size)
				reader := bytes.NewReader(helloData)
				if err := mount.filesys.cfs.Stage("/"+name, reader); err != nil {
					t.Fatalf("Adding simple file from reader failed: %v", err)
				}

				path := filepath.Join(mount.Dir, name)
				checkForCorrectFile(t, path, helloData)
			})
		}
	})
}

func TestWrite(t *testing.T) {
	withMount(t, func(mount *Mount) {
		for _, size := range DataSizes {
			helloData := testutil.CreateDummyBuf(size)
			path := filepath.Join(mount.Dir, fmt.Sprintf("hello_%d", size))

			// Write a simple file via the fuse layer:
			err := ioutil.WriteFile(path, helloData, 0644)
			if err != nil {
				t.Fatalf("Could not write simple file via fuse layer: %v", err)
			}

			checkForCorrectFile(t, path, helloData)
		}
	})
}

// Regression test for copying larger file to the mount.
func TestTouchWrite(t *testing.T) {
	withMount(t, func(mount *Mount) {
		for _, size := range DataSizes {
			name := fmt.Sprintf("/empty_%d", size)
			if err := mount.filesys.cfs.Touch(name); err != nil {
				t.Fatalf("Could not touch an empty file: %v", err)
			}

			path := filepath.Join(mount.Dir, name)

			// Write a simple file via the fuse layer:
			helloData := testutil.CreateDummyBuf(size)
			err := ioutil.WriteFile(path, helloData, 0644)
			if err != nil {
				t.Fatalf("Could not write simple file via fuse layer: %v", err)
			}

			checkForCorrectFile(t, path, helloData)
		}
	})
}

// Regression test for copying a file to a subdirectory.
func TestTouchWriteSubdir(t *testing.T) {
	withMount(t, func(mount *Mount) {
		subDirPath := filepath.Join(mount.Dir, "sub")
		require.Nil(t, os.Mkdir(subDirPath, 0644))

		expected := []byte{1, 2, 3}
		filePath := filepath.Join(subDirPath, "donald.png")
		require.Nil(t, ioutil.WriteFile(filePath, expected, 0644))

		got, err := ioutil.ReadFile(filePath)
		require.Nil(t, err)
		require.Equal(t, expected, got)
	})
}
<commit_msg>fuse: fix crash in test<commit_after>package fuse

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	log "github.com/Sirupsen/logrus"
	"github.com/sahib/brig/catfs"
	"github.com/sahib/brig/defaults"
	"github.com/sahib/brig/util/testutil"
	"github.com/sahib/config"
	"github.com/stretchr/testify/require"
)

func init() {
	// NOTE: This is useful for debugging.
	log.SetLevel(log.DebugLevel)
}

func withDummyFS(t *testing.T, fn func(fs *catfs.FS)) {
	backend := catfs.NewMemFsBackend()
	owner := "alice"

	dbPath, err := ioutil.TempDir("", "brig-fs-test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}

	defer os.RemoveAll(dbPath)

	cfg, err := config.Open(nil, defaults.Defaults)
	require.Nil(t, err)

	fs, err := catfs.NewFilesystem(backend, dbPath, owner, false, cfg)
	if err != nil {
		t.Fatalf("Failed to create filesystem: %v", err)
	}

	fn(fs)

	if err := fs.Close(); err != nil {
		t.Fatalf("Failed to close filesystem: %v", err)
	}
}

func withMount(t *testing.T, f func(mount *Mount)) {
	mntPath := filepath.Join(os.TempDir(), "brig-fuse-mountdir")

	if err := os.MkdirAll(mntPath, 0777); err != nil {
		t.Fatalf("Unable to create empty mount dir: %v", err)
	}

	defer testutil.Remover(t, mntPath)

	withDummyFS(t, func(fs *catfs.FS) {
		mount, err := NewMount(fs, mntPath)
		if err != nil {
			t.Fatalf("Cannot create mount: %v", err)
		}

		f(mount)

		if err := mount.Close(); err != nil {
			t.Fatalf("Closing mount failed: %v", err)
		}
	})
}

func checkForCorrectFile(t *testing.T, path string, data []byte) {
	// Try to read it over fuse:
	helloBuffer := &bytes.Buffer{}
	fd, err := os.Open(path)
	if err != nil {
		t.Fatalf("Unable to open simple file over fuse: %v", err)
	}

	defer func() {
		if err := fd.Close(); err != nil {
			t.Fatalf("Unable to close simple file over fuse: %v", err)
		}
	}()

	n, err := io.CopyBuffer(helloBuffer, fd, make([]byte, 128*1024))
	if err != nil {
		t.Fatalf("Unable to read full simple file over fuse: %v", err)
	}

	if n != int64(len(data)) {
		t.Fatalf("Data differs over fuse: got %d, should be %d bytes", n, len(data))
	}

	if !bytes.Equal(helloBuffer.Bytes(), data) {
		t.Errorf(
			"Data from simple file does not match source. Len: %d %d",
			len(data),
			helloBuffer.Len(),
		)

		require.Equal(t, data, helloBuffer.Bytes())
	}
}

var (
	DataSizes = []int64{
		0, 1, 2, 4, 8, 16, 32, 64, 1024,
		2048, 4095, 4096, 4097, 147611,
	}
)

func TestRead(t *testing.T) {
	withMount(t, func(mount *Mount) {
		for _, size := range DataSizes {
			t.Run(fmt.Sprintf("%d", size), func(t *testing.T) {
				helloData := testutil.CreateDummyBuf(size)

				// Add a simple file:
				name := fmt.Sprintf("hello_%d", size)
				reader := bytes.NewReader(helloData)
				if err := mount.filesys.cfs.Stage("/"+name, reader); err != nil {
					t.Fatalf("Adding simple file from reader failed: %v", err)
				}

				path := filepath.Join(mount.Dir, name)
				checkForCorrectFile(t, path, helloData)
			})
		}
	})
}

func TestWrite(t *testing.T) {
	withMount(t, func(mount *Mount) {
		for _, size := range DataSizes {
			helloData := testutil.CreateDummyBuf(size)
			path := filepath.Join(mount.Dir, fmt.Sprintf("hello_%d", size))

			// Write a simple file via the fuse layer:
			err := ioutil.WriteFile(path, helloData, 0644)
			if err != nil {
				t.Fatalf("Could not write simple file via fuse layer: %v", err)
			}

			checkForCorrectFile(t, path, helloData)
		}
	})
}

// Regression test for copying larger file to the mount.
func TestTouchWrite(t *testing.T) {
	withMount(t, func(mount *Mount) {
		for _, size := range DataSizes {
			name := fmt.Sprintf("/empty_%d", size)
			if err := mount.filesys.cfs.Touch(name); err != nil {
				t.Fatalf("Could not touch an empty file: %v", err)
			}

			path := filepath.Join(mount.Dir, name)

			// Write a simple file via the fuse layer:
			helloData := testutil.CreateDummyBuf(size)
			err := ioutil.WriteFile(path, helloData, 0644)
			if err != nil {
				t.Fatalf("Could not write simple file via fuse layer: %v", err)
			}

			checkForCorrectFile(t, path, helloData)
		}
	})
}

// Regression test for copying a file to a subdirectory.
func TestTouchWriteSubdir(t *testing.T) {
	withMount(t, func(mount *Mount) {
		subDirPath := filepath.Join(mount.Dir, "sub")
		require.Nil(t, os.Mkdir(subDirPath, 0644))

		expected := []byte{1, 2, 3}
		filePath := filepath.Join(subDirPath, "donald.png")
		require.Nil(t, ioutil.WriteFile(filePath, expected, 0644))

		got, err := ioutil.ReadFile(filePath)
		require.Nil(t, err)
		require.Equal(t, expected, got)
	})
}
<|endoftext|>


<commit_before>package image

import (
	"bytes"
	"encoding/binary"
	"errors"
	_ "log" // for debug
	"os"
)

// BitmapFileHeader is for BMP file header
type BitmapFileHeader struct {
	bfType      [2]byte // must always be 'BM'
	bfSize      uint32
	bfReserved1 uint16 // reserved, should be '0'
	bfReserved2 uint16 // reserved, should be '0'
	bfOffBits   uint32
}

// BitmapInfoHeader is for BMP info header
type BitmapInfoHeader struct {
	bitSize         uint32
	biWidth         uint32
	biHeight        uint32
	biPlanes        uint16
	biBitCount      uint16
	biCompression   uint32
	biSizeImage     uint32
	biXPelsPerMeter uint32
	biYPelsPerMeter uint32
	biClrUsed       uint32
	biClrImportant  uint32
}

// DcmImage provide the "DICOM image toolkit"
type DcmImage struct {
	Rows                      uint32
	Columns                   uint32
	PixelWidth                float64
	PixelHeight               float64
	BitsAllocated             uint16
	BitsStored                uint16
	HighBit                   uint16
	PhotometricInterpretation string
	SamplesPerPixel           uint16
	PixelRepresentation       uint16
	PlanarConfiguration       uint16
	RescaleIntercept          float64
	RescaleSlope              float64
	WindowCenter              float64
	WindowWidth               float64

	RescaleType          string
	PresentationLUTShape string

	minValue int16
	maxValue int16

	PixelData []byte
}

// WriteBMP write pixel data to BMP file
func (image DcmImage) WriteBMP(filename string, bits uint16, frame int) error {
	switch bits {
	case 8:
	case 24:
	case 32:
	default:
		err := errors.New("not supported BMP format")
		return err
	}
	var fileHeader BitmapFileHeader
	fileHeader.bfType[0] = 'B'
	fileHeader.bfType[1] = 'M'
	fileHeader.bfSize = 54 + uint32(image.Rows*image.Columns)
	fileHeader.bfReserved1 = 0
	fileHeader.bfReserved2 = 0
	fileHeader.bfOffBits = 54

	var palette *[256]uint32
	if bits == 8 {
		palette = new([256]uint32)
		fileHeader.bfSize += 1024
		fileHeader.bfOffBits += 1024
		for i := uint32(0); i < 256; i++ {
			palette[i] = uint32((i << 16) | (i << 8) | i)
		}
	}

	var infoHeader BitmapInfoHeader
	infoHeader.bitSize = 40
	infoHeader.biWidth = image.Columns
	infoHeader.biHeight = image.Rows
	infoHeader.biPlanes = 1
	infoHeader.biBitCount = bits
	infoHeader.biCompression = 0
	infoHeader.biSizeImage = uint32((uint32(infoHeader.biWidth)*uint32(infoHeader.biBitCount) + 31) / 32 * 4 * infoHeader.biHeight)
	infoHeader.biXPelsPerMeter = 0
	infoHeader.biYPelsPerMeter = 0
	infoHeader.biClrUsed = 0
	infoHeader.biClrImportant = 0

	f, _ := os.Create(filename)
	defer f.Close()

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, fileHeader)
	binary.Write(buf, binary.LittleEndian, infoHeader)

	if palette != nil {
		binary.Write(buf, binary.LittleEndian, palette)
	}

	data := image.convertTo8Bit()

	binary.Write(buf, binary.LittleEndian, data)
	f.Write(buf.Bytes())
	return nil
}

// ClipHighBits clip the high bits
func (image DcmImage) ClipHighBits(pixel int16) int16 {
	if image.HighBit > 15 {
		return pixel
	}
	nMask := 0xffff << (image.HighBit + 1)
	if image.PixelRepresentation == 0 {
		nSignBit := 1 << image.HighBit
		if (pixel & int16(nSignBit)) != 0 {
			pixel |= int16(nMask)
			return pixel
		}
	}
	pixel &= ^int16(nMask)
	return pixel
}

// RescalePixel rescale the pixel data ,especially for CT
func (image DcmImage) RescalePixel(pixel int16) int16 {
	if image.RescaleSlope == 1.0 && image.RescaleIntercept == 0.0 {
		return pixel
	}
	return int16(float64(pixel)*image.RescaleSlope + image.RescaleIntercept)
}

// RescaleWindowLevel rescale the window level to 8 bit
func (image DcmImage) RescaleWindowLevel(pixel int16) uint8 {
	var value float64
	if (image.WindowCenter == 0.0) && (image.WindowWidth == 0.0) {
		var slope float64
		if image.minValue != image.maxValue {
			slope = 255.0 / float64(image.maxValue-image.minValue)
		} else {
			slope = 1.0
		}
		value = float64(pixel-image.minValue) * slope
	} else {

		shift := image.WindowCenter - image.WindowWidth/2.0
		slope := 255.0 / image.WindowWidth
		value = (float64(pixel) - shift) * slope
	}
	var result uint8
	if value < 0 {
		result = 0
	} else if value > 255 {
		result = 255
	} else {
		result = uint8(value)
	}
	return result
}

func (image DcmImage) convertTo8Bit() []uint8 {
	var result []uint8
	count := image.Rows * image.Columns
	image.findPixelExtremeValue()

	for i := uint32(0); i < count; i++ {
		p := binary.LittleEndian.Uint16(image.PixelData[2*i : 2*i+2])

		pixel := image.ClipHighBits(int16(p))
		pixel = image.RescalePixel(pixel)

		b := image.RescaleWindowLevel(pixel)
		result = append(result, b)
	}
	return result
}

func (image DcmImage) findPixelExtremeValue() {
	// skip to find the max/min value if window level is not 0
	if (image.WindowCenter != 0.0) || (image.WindowWidth != 0.0) {
		return
	}
	count := image.Columns * image.Rows
	for i := uint32(0); i < count; i++ {
		p := int16(binary.LittleEndian.Uint16(image.PixelData[2*i : 2*i+2]))
		if i == 0 {
			image.minValue = p
			image.maxValue = p
		}
		if p < image.minValue {
			image.minValue = p
		}
		if p > image.maxValue {
			image.maxValue = p
		}
	}
}
<commit_msg>change the functions to private.<commit_after>package image

import (
	"bytes"
	"encoding/binary"
	"errors"
	_ "log" // for debug
	"os"
)

// BitmapFileHeader is for BMP file header
type BitmapFileHeader struct {
	bfType      [2]byte // must always be 'BM'
	bfSize      uint32
	bfReserved1 uint16 // reserved, should be '0'
	bfReserved2 uint16 // reserved, should be '0'
	bfOffBits   uint32
}

// BitmapInfoHeader is for BMP info header
type BitmapInfoHeader struct {
	bitSize         uint32
	biWidth         uint32
	biHeight        uint32
	biPlanes        uint16
	biBitCount      uint16
	biCompression   uint32
	biSizeImage     uint32
	biXPelsPerMeter uint32
	biYPelsPerMeter uint32
	biClrUsed       uint32
	biClrImportant  uint32
}

// DcmImage provide the "DICOM image toolkit"
type DcmImage struct {
	Rows                      uint32
	Columns                   uint32
	PixelWidth                float64
	PixelHeight               float64
	BitsAllocated             uint16
	BitsStored                uint16
	HighBit                   uint16
	PhotometricInterpretation string
	SamplesPerPixel           uint16
	PixelRepresentation       uint16
	PlanarConfiguration       uint16
	RescaleIntercept          float64
	RescaleSlope              float64
	WindowCenter              float64
	WindowWidth               float64

	RescaleType          string
	PresentationLUTShape string

	minValue int16
	maxValue int16

	PixelData []byte
}

// WriteBMP write pixel data to BMP file
func (image DcmImage) WriteBMP(filename string, bits uint16, frame int) error {
	switch bits {
	case 8:
	case 24:
	case 32:
	default:
		err := errors.New("not supported BMP format")
		return err
	}
	var fileHeader BitmapFileHeader
	fileHeader.bfType[0] = 'B'
	fileHeader.bfType[1] = 'M'
	fileHeader.bfSize = 54 + uint32(image.Rows*image.Columns)
	fileHeader.bfReserved1 = 0
	fileHeader.bfReserved2 = 0
	fileHeader.bfOffBits = 54

	var palette *[256]uint32
	if bits == 8 {
		palette = new([256]uint32)
		fileHeader.bfSize += 1024
		fileHeader.bfOffBits += 1024
		for i := uint32(0); i < 256; i++ {
			palette[i] = uint32((i << 16) | (i << 8) | i)
		}
	}

	var infoHeader BitmapInfoHeader
	infoHeader.bitSize = 40
	infoHeader.biWidth = image.Columns
	infoHeader.biHeight = image.Rows
	infoHeader.biPlanes = 1
	infoHeader.biBitCount = bits
	infoHeader.biCompression = 0
	infoHeader.biSizeImage = uint32((uint32(infoHeader.biWidth)*uint32(infoHeader.biBitCount) + 31) / 32 * 4 * infoHeader.biHeight)
	infoHeader.biXPelsPerMeter = 0
	infoHeader.biYPelsPerMeter = 0
	infoHeader.biClrUsed = 0
	infoHeader.biClrImportant = 0

	f, _ := os.Create(filename)
	defer f.Close()

	buf := new(bytes.Buffer)
	binary.Write(buf, binary.LittleEndian, fileHeader)
	binary.Write(buf, binary.LittleEndian, infoHeader)

	if palette != nil {
		binary.Write(buf, binary.LittleEndian, palette)
	}

	data := image.convertTo8Bit()

	binary.Write(buf, binary.LittleEndian, data)
	f.Write(buf.Bytes())
	return nil
}

func (image DcmImage) clipHighBits(pixel int16) int16 {
	if image.HighBit > 15 {
		return pixel
	}
	nMask := 0xffff << (image.HighBit + 1)
	if image.PixelRepresentation == 0 {
		nSignBit := 1 << image.HighBit
		if (pixel & int16(nSignBit)) != 0 {
			pixel |= int16(nMask)
			return pixel
		}
	}
	pixel &= ^int16(nMask)
	return pixel
}

func (image DcmImage) rescalePixel(pixel int16) int16 {
	if image.RescaleSlope == 1.0 && image.RescaleIntercept == 0.0 {
		return pixel
	}
	return int16(float64(pixel)*image.RescaleSlope + image.RescaleIntercept)
}

func (image DcmImage) rescaleWindowLevel(pixel int16) uint8 {
	var value float64
	if (image.WindowCenter == 0.0) && (image.WindowWidth == 0.0) {
		var slope float64
		if image.minValue != image.maxValue {
			slope = 255.0 / float64(image.maxValue-image.minValue)
		} else {
			slope = 1.0
		}
		value = float64(pixel-image.minValue) * slope
	} else {

		shift := image.WindowCenter - image.WindowWidth/2.0
		slope := 255.0 / image.WindowWidth
		value = (float64(pixel) - shift) * slope
	}
	var result uint8
	if value < 0 {
		result = 0
	} else if value > 255 {
		result = 255
	} else {
		result = uint8(value)
	}
	return result
}

func (image DcmImage) convertTo8Bit() []uint8 {
	var result []uint8
	count := image.Rows * image.Columns
	image.findPixelExtremeValue()

	for i := uint32(0); i < count; i++ {
		p := binary.LittleEndian.Uint16(image.PixelData[2*i : 2*i+2])

		pixel := image.clipHighBits(int16(p))
		pixel = image.rescalePixel(pixel)

		b := image.rescaleWindowLevel(pixel)
		result = append(result, b)
	}
	return result
}

func (image DcmImage) findPixelExtremeValue() {
	// skip to find the max/min value if window level is not 0
	if (image.WindowCenter != 0.0) || (image.WindowWidth != 0.0) {
		return
	}
	count := image.Columns * image.Rows
	for i := uint32(0); i < count; i++ {
		p := int16(binary.LittleEndian.Uint16(image.PixelData[2*i : 2*i+2]))
		if i == 0 {
			image.minValue = p
			image.maxValue = p
		}
		if p < image.minValue {
			image.minValue = p
		}
		if p > image.maxValue {
			image.maxValue = p
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 CloudAwan LLC
//
// 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 image

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/cloudawan/cloudone/authorization"
	"github.com/cloudawan/cloudone/host"
	"github.com/cloudawan/cloudone/utility/configuration"
	"github.com/cloudawan/cloudone_utility/restclient"
	"github.com/cloudawan/cloudone_utility/sshclient"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type ImageIdentifier struct {
	Repository string
	Tag        string
}

func DeleteImageInformationAndRelatedRecord(imageInformationName string) error {
	imageRecordSlice, err := GetStorage().LoadImageRecordWithImageInformationName(imageInformationName)
	if err != nil {
		log.Error(err)
		return err
	}

	imageIdentifierSlice := make([]ImageIdentifier, 0)
	for _, imageRecord := range imageRecordSlice {
		repository := imageRecord.Path[:len(imageRecord.Path)-(len(imageRecord.Version)+1)] // Remove :version. +1 due to :
		imageIdentifierSlice = append(imageIdentifierSlice, ImageIdentifier{
			repository,
			imageRecord.Version,
		})
	}

	hasError := false
	buffer := bytes.Buffer{}

	err = RemoveImageFromPrivateRegistry(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RemoveImageFromAllHost(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = GetStorage().DeleteImageInformationAndRelatedRecord(imageInformationName)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RequestDeleteBuildLogBelongingToImageInformation(imageInformationName)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	if hasError {
		log.Error(buffer.String())
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

func DeleteImageRecord(imageInformationName string, imageRecordVersion string) error {
	imageRecord, err := GetStorage().LoadImageRecord(imageInformationName, imageRecordVersion)
	if err != nil {
		log.Error(err)
		return err
	}

	repository := imageRecord.Path[:len(imageRecord.Path)-(len(imageRecord.Version)+1)] // Remove :version. +1 due to :

	imageIdentifierSlice := make([]ImageIdentifier, 0)
	imageIdentifierSlice = append(imageIdentifierSlice, ImageIdentifier{
		repository,
		imageRecord.Version,
	})

	hasError := false
	buffer := bytes.Buffer{}

	err = RemoveImageFromPrivateRegistry(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RemoveImageFromAllHost(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = GetStorage().DeleteImageRecord(imageInformationName, imageRecordVersion)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RequestDeleteBuildLog(imageInformationName, imageRecordVersion)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	if hasError {
		log.Error(buffer.String())
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

// Due to the docker registry API, the delete is only make it unavailable but the image is not removed from storage.
func RemoveImageFromPrivateRegistry(imageIdentifierSlice []ImageIdentifier) error {
	hasError := false
	buffer := bytes.Buffer{}
	for _, imageIdentifier := range imageIdentifierSlice {
		splitSlice := strings.Split(imageIdentifier.Repository, "/")
		if len(splitSlice) != 2 {
			hasError = true
			errorMessage := fmt.Sprintf("Invalid repository format %v.", imageIdentifier.Repository)
			log.Error(errorMessage)
			buffer.WriteString(errorMessage)
		} else {
			hostAndPort := splitSlice[0]
			repositoryName := splitSlice[1]

			request, err := http.NewRequest("GET", "https://"+hostAndPort+"/v2/"+repositoryName+"/manifests/"+imageIdentifier.Tag, nil)
			if err != nil {
				hasError = true
				errorMessage := fmt.Sprintf("Error during creating the request with imageIdentifier %v error %v.", imageIdentifier, err)
				log.Error(errorMessage)
				buffer.WriteString(errorMessage)
			} else {
				response, err := restclient.GetInsecureHTTPSClient().Do(request)
				if err != nil {
					hasError = true
					errorMessage := fmt.Sprintf("Error during the request with imageIdentifier %v error %v.", imageIdentifier, err)
					log.Error(errorMessage)
					buffer.WriteString(errorMessage)
				} else {
					digest := response.Header.Get("Docker-Content-Digest")

					_, err := restclient.RequestDelete("https://"+hostAndPort+"/v2/"+repositoryName+"/manifests/"+digest, nil, nil, false)
					if err != nil {
						hasError = true
						errorMessage := fmt.Sprintf("Delete imageIdentifier %v error %v.", imageIdentifier, err)
						log.Error(errorMessage)
						buffer.WriteString(errorMessage)
					}
				}
			}
		}
	}

	if hasError {
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

func RemoveImageFromAllHost(imageIdentifierSlice []ImageIdentifier) error {
	credentialSlice, err := host.GetStorage().LoadAllCredential()
	if err != nil {
		log.Error(err)
		return err
	}

	amount := len(imageIdentifierSlice)

	commandSlice := make([]string, 0)
	for _, imageIdentifier := range imageIdentifierSlice {
		commandSlice = append(commandSlice, "sudo docker rmi -f "+imageIdentifier.Repository+":"+imageIdentifier.Tag+"\n")
	}

	hasError := false
	buffer := bytes.Buffer{}
	for _, credential := range credentialSlice {
		interactiveMap := make(map[string]string)
		interactiveMap["[sudo]"] = credential.SSH.Password + "\n"

		resultSlice, err := sshclient.InteractiveSSH(
			2*time.Second,
			time.Duration(amount)*time.Minute,
			credential.IP,
			credential.SSH.Port,
			credential.SSH.User,
			credential.SSH.Password,
			commandSlice,
			interactiveMap)

		log.Info("Issue command via ssh with result:\n %v", resultSlice)

		if err != nil {
			hasError = true
			errorMessage := fmt.Sprintf("Error message: %v Result Output: %v .", err, resultSlice)
			log.Error(errorMessage)
			buffer.WriteString(errorMessage)
		}
	}

	if hasError {
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

func RequestDeleteBuildLogBelongingToImageInformation(imageInformationName string) error {
	cloudoneAnalysisHost, ok := configuration.LocalConfiguration.GetString("cloudoneAnalysisHost")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisHost")
		return errors.New("Fail to get configuration cloudoneAnalysisHost")
	}
	cloudoneAnalysisPort, ok := configuration.LocalConfiguration.GetInt("cloudoneAnalysisPort")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisPort")
		return errors.New("Fail to get configuration cloudoneAnalysisPort")
	}

	url := "https://" + cloudoneAnalysisHost + ":" + strconv.Itoa(cloudoneAnalysisPort) + "/api/v1/buildlogs/" + imageInformationName

	headerMap := make(map[string]string)
	headerMap["token"] = authorization.SystemAdminToken

	_, err := restclient.RequestDelete(url, nil, headerMap, false)
	if err != nil {
		log.Error("Fail to request delete build image information %s log with error %s", imageInformationName, err)
	}

	return err
}

func RequestDeleteBuildLog(imageInformationName string, imageRecordVersion string) error {
	cloudoneAnalysisHost, ok := configuration.LocalConfiguration.GetString("cloudoneAnalysisHost")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisHost")
		return errors.New("Fail to get configuration cloudoneAnalysisHost")
	}
	cloudoneAnalysisPort, ok := configuration.LocalConfiguration.GetInt("cloudoneAnalysisPort")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisPort")
		return errors.New("Fail to get configuration cloudoneAnalysisPort")
	}

	url := "https://" + cloudoneAnalysisHost + ":" + strconv.Itoa(cloudoneAnalysisPort) + "/api/v1/buildlogs/" + imageInformationName + "/" + imageRecordVersion

	headerMap := make(map[string]string)
	headerMap["token"] = authorization.SystemAdminToken

	_, err := restclient.RequestDelete(url, nil, headerMap, false)
	if err != nil {
		log.Error("Fail to request delete build image information %s version %s log with error %s", imageInformationName, imageRecordVersion, err)
	}

	return err
}
<commit_msg>For docker compatible issue<commit_after>// Copyright 2015 CloudAwan LLC
//
// 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 image

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/cloudawan/cloudone/authorization"
	"github.com/cloudawan/cloudone/host"
	"github.com/cloudawan/cloudone/utility/configuration"
	"github.com/cloudawan/cloudone_utility/restclient"
	"github.com/cloudawan/cloudone_utility/sshclient"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type ImageIdentifier struct {
	Repository string
	Tag        string
}

func DeleteImageInformationAndRelatedRecord(imageInformationName string) error {
	imageRecordSlice, err := GetStorage().LoadImageRecordWithImageInformationName(imageInformationName)
	if err != nil {
		log.Error(err)
		return err
	}

	imageIdentifierSlice := make([]ImageIdentifier, 0)
	for _, imageRecord := range imageRecordSlice {
		repository := imageRecord.Path[:len(imageRecord.Path)-(len(imageRecord.Version)+1)] // Remove :version. +1 due to :
		imageIdentifierSlice = append(imageIdentifierSlice, ImageIdentifier{
			repository,
			imageRecord.Version,
		})
	}

	hasError := false
	buffer := bytes.Buffer{}

	err = RemoveImageFromPrivateRegistry(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RemoveImageFromAllHost(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = GetStorage().DeleteImageInformationAndRelatedRecord(imageInformationName)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RequestDeleteBuildLogBelongingToImageInformation(imageInformationName)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	if hasError {
		log.Error(buffer.String())
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

func DeleteImageRecord(imageInformationName string, imageRecordVersion string) error {
	imageRecord, err := GetStorage().LoadImageRecord(imageInformationName, imageRecordVersion)
	if err != nil {
		log.Error(err)
		return err
	}

	repository := imageRecord.Path[:len(imageRecord.Path)-(len(imageRecord.Version)+1)] // Remove :version. +1 due to :

	imageIdentifierSlice := make([]ImageIdentifier, 0)
	imageIdentifierSlice = append(imageIdentifierSlice, ImageIdentifier{
		repository,
		imageRecord.Version,
	})

	hasError := false
	buffer := bytes.Buffer{}

	err = RemoveImageFromPrivateRegistry(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RemoveImageFromAllHost(imageIdentifierSlice)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = GetStorage().DeleteImageRecord(imageInformationName, imageRecordVersion)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	err = RequestDeleteBuildLog(imageInformationName, imageRecordVersion)
	if err != nil {
		hasError = true
		buffer.WriteString(err.Error())
	}

	if hasError {
		log.Error(buffer.String())
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

// Due to the docker registry API, the delete is only make it unavailable but the image is not removed from storage.
func RemoveImageFromPrivateRegistry(imageIdentifierSlice []ImageIdentifier) error {
	hasError := false
	buffer := bytes.Buffer{}
	for _, imageIdentifier := range imageIdentifierSlice {
		splitSlice := strings.Split(imageIdentifier.Repository, "/")
		if len(splitSlice) != 2 {
			hasError = true
			errorMessage := fmt.Sprintf("Invalid repository format %v.", imageIdentifier.Repository)
			log.Error(errorMessage)
			buffer.WriteString(errorMessage)
		} else {
			hostAndPort := splitSlice[0]
			repositoryName := splitSlice[1]

			request, err := http.NewRequest("GET", "https://"+hostAndPort+"/v2/"+repositoryName+"/manifests/"+imageIdentifier.Tag, nil)
			if err != nil {
				hasError = true
				errorMessage := fmt.Sprintf("Error during creating the request with imageIdentifier %v error %v.", imageIdentifier, err)
				log.Error(errorMessage)
				buffer.WriteString(errorMessage)
			} else {
				// For registry version 2.3 and later
				request.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
				response, err := restclient.GetInsecureHTTPSClient().Do(request)
				if err != nil {
					hasError = true
					errorMessage := fmt.Sprintf("Error during the request with imageIdentifier %v error %v.", imageIdentifier, err)
					log.Error(errorMessage)
					buffer.WriteString(errorMessage)
				} else {
					digest := response.Header.Get("Docker-Content-Digest")

					_, err := restclient.RequestDelete("https://"+hostAndPort+"/v2/"+repositoryName+"/manifests/"+digest, nil, nil, false)
					if err != nil {
						hasError = true
						errorMessage := fmt.Sprintf("Delete imageIdentifier %v error %v.", imageIdentifier, err)
						log.Error(errorMessage)
						buffer.WriteString(errorMessage)
					}
				}
			}
		}
	}

	if hasError {
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

func RemoveImageFromAllHost(imageIdentifierSlice []ImageIdentifier) error {
	credentialSlice, err := host.GetStorage().LoadAllCredential()
	if err != nil {
		log.Error(err)
		return err
	}

	amount := len(imageIdentifierSlice)

	commandSlice := make([]string, 0)
	for _, imageIdentifier := range imageIdentifierSlice {
		commandSlice = append(commandSlice, "sudo docker rmi -f "+imageIdentifier.Repository+":"+imageIdentifier.Tag+"\n")
	}

	hasError := false
	buffer := bytes.Buffer{}
	for _, credential := range credentialSlice {
		interactiveMap := make(map[string]string)
		interactiveMap["[sudo]"] = credential.SSH.Password + "\n"

		resultSlice, err := sshclient.InteractiveSSH(
			2*time.Second,
			time.Duration(amount)*time.Minute,
			credential.IP,
			credential.SSH.Port,
			credential.SSH.User,
			credential.SSH.Password,
			commandSlice,
			interactiveMap)

		log.Info("Issue command via ssh with result:\n %v", resultSlice)

		if err != nil {
			hasError = true
			errorMessage := fmt.Sprintf("Error message: %v Result Output: %v .", err, resultSlice)
			log.Error(errorMessage)
			buffer.WriteString(errorMessage)
		}
	}

	if hasError {
		return errors.New(buffer.String())
	} else {
		return nil
	}
}

func RequestDeleteBuildLogBelongingToImageInformation(imageInformationName string) error {
	cloudoneAnalysisHost, ok := configuration.LocalConfiguration.GetString("cloudoneAnalysisHost")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisHost")
		return errors.New("Fail to get configuration cloudoneAnalysisHost")
	}
	cloudoneAnalysisPort, ok := configuration.LocalConfiguration.GetInt("cloudoneAnalysisPort")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisPort")
		return errors.New("Fail to get configuration cloudoneAnalysisPort")
	}

	url := "https://" + cloudoneAnalysisHost + ":" + strconv.Itoa(cloudoneAnalysisPort) + "/api/v1/buildlogs/" + imageInformationName

	headerMap := make(map[string]string)
	headerMap["token"] = authorization.SystemAdminToken

	_, err := restclient.RequestDelete(url, nil, headerMap, false)
	if err != nil {
		log.Error("Fail to request delete build image information %s log with error %s", imageInformationName, err)
	}

	return err
}

func RequestDeleteBuildLog(imageInformationName string, imageRecordVersion string) error {
	cloudoneAnalysisHost, ok := configuration.LocalConfiguration.GetString("cloudoneAnalysisHost")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisHost")
		return errors.New("Fail to get configuration cloudoneAnalysisHost")
	}
	cloudoneAnalysisPort, ok := configuration.LocalConfiguration.GetInt("cloudoneAnalysisPort")
	if ok == false {
		log.Error("Fail to get configuration cloudoneAnalysisPort")
		return errors.New("Fail to get configuration cloudoneAnalysisPort")
	}

	url := "https://" + cloudoneAnalysisHost + ":" + strconv.Itoa(cloudoneAnalysisPort) + "/api/v1/buildlogs/" + imageInformationName + "/" + imageRecordVersion

	headerMap := make(map[string]string)
	headerMap["token"] = authorization.SystemAdminToken

	_, err := restclient.RequestDelete(url, nil, headerMap, false)
	if err != nil {
		log.Error("Fail to request delete build image information %s version %s log with error %s", imageInformationName, imageRecordVersion, err)
	}

	return err
}
<|endoftext|>


<commit_before>package game

import (
	"sync"
	"time"

	"github.com/ivan1993spb/snake-server/engine"
	"github.com/ivan1993spb/snake-server/playground"
)

const worldEventsBufferSize = 512

const worldEventsTimeout = time.Second

const worldEventsNumberLimit = 128

type World interface {
	ObjectExists(object interface{}) bool
	LocationExists(location engine.Location) bool
	EntityExists(object interface{}, location engine.Location) bool
	GetObjectByLocation(location engine.Location) interface{}
	GetObjectByDot(dot *engine.Dot) interface{}
	GetEntityByDot(dot *engine.Dot) (interface{}, engine.Location)
	GetObjectsByDots(dots []*engine.Dot) []interface{}
	CreateObject(object interface{}, location engine.Location) error
	CreateObjectAvailableDots(object interface{}, location engine.Location) (engine.Location, *playground.ErrCreateObjectAvailableDots)
	DeleteObject(object interface{}, location engine.Location) *playground.ErrDeleteObject
	UpdateObject(object interface{}, old, new engine.Location) *playground.ErrUpdateObject
	UpdateObjectAvailableDots(object interface{}, old, new engine.Location) (engine.Location, *playground.ErrUpdateObjectAvailableDots)
	CreateObjectRandomDot(object interface{}) (engine.Location, error)
	CreateObjectRandomRect(object interface{}, rw, rh uint8) (engine.Location, error)
	Navigate(dot *engine.Dot, dir engine.Direction, dis uint8) (*engine.Dot, error)
	Size() uint16
	Width() uint8
	Height() uint8
}

type world struct {
	pg      *playground.Playground
	ch      chan Event
	chs     []chan Event
	chsMux  *sync.RWMutex
	stop    chan struct{}
	timeout time.Duration
}

func newWorld(pg *playground.Playground) *world {
	return &world{
		pg:      pg,
		ch:      make(chan Event, worldEventsBufferSize),
		chs:     make([]chan Event, 0),
		chsMux:  &sync.RWMutex{},
		stop:    make(chan struct{}, 0),
		timeout: worldEventsTimeout,
	}
}

func (w *world) run() {
	go func() {
		for {
			select {
			case event := <-w.ch:
				w.broadcast(event)
			case <-w.stop:
				return
			}
		}
	}()
}

func (w *world) broadcast(event Event) {
	w.chsMux.RLock()
	for _, ch := range w.chs {
		var timer = time.NewTimer(w.timeout)
		select {
		case ch <- event:
		case <-w.stop:
			return
		case <-timer.C:
		}
		timer.Stop()
	}
	w.chsMux.RUnlock()
}

// TODO: Create reset by count events in awaiting
func (w *world) event(event Event) {
	if len(w.ch) == worldEventsBufferSize {
		// TODO: Create warning messages?
		<-w.ch
	}
	if worldEventsBufferSize == 0 {
		// TODO: Async?
		var timer = time.NewTimer(worldEventsTimeout)
		select {
		case w.ch <- event:
		case <-w.stop:
		case <-timer.C:
		}
		timer.Stop()
	} else {
		w.ch <- event
	}
}

func (w *world) RunObserver(observer interface {
	Run(<-chan Event)
}) {
	ch := make(chan Event, worldEventsBufferSize)

	w.chsMux.Lock()
	w.chs = append(w.chs, ch)
	w.chsMux.Unlock()

	observer.Run(ch)

	w.chsMux.Lock()
	for i := range w.chs {
		if w.chs[i] == ch {
			w.chs = append(w.chs[:i], w.chs[i+1:]...)
			close(ch)
			break
		}
	}
	w.chsMux.Unlock()
}

func (w *world) Stop() {
	close(w.stop)
	close(w.ch)

	w.chsMux.Lock()
	defer w.chsMux.Unlock()

	for _, ch := range w.chs {
		close(ch)
	}

	w.chs = w.chs[:0]
}

func (w *world) ObjectExists(object interface{}) bool {
	return w.pg.ObjectExists(object)
}

func (w *world) LocationExists(location engine.Location) bool {
	return w.pg.LocationExists(location)
}

func (w *world) EntityExists(object interface{}, location engine.Location) bool {
	return w.pg.EntityExists(object, location)
}

func (w *world) GetObjectByLocation(location engine.Location) interface{} {
	if object := w.pg.GetObjectByLocation(location); object != nil {
		w.event(Event{
			Type:    EventTypeObjectChecked,
			Payload: object,
		})
		return object
	}
	return nil

}

func (w *world) GetObjectByDot(dot *engine.Dot) interface{} {
	if object := w.pg.GetObjectByDot(dot); object != nil {
		w.event(Event{
			Type:    EventTypeObjectChecked,
			Payload: object,
		})
		return object
	}
	return nil
}

func (w *world) GetEntityByDot(dot *engine.Dot) (interface{}, engine.Location) {
	if object, location := w.pg.GetEntityByDot(dot); object != nil && !location.Empty() {
		w.event(Event{
			Type:    EventTypeObjectChecked,
			Payload: object,
		})
		return object, location
	}
	return nil, nil
}

func (w *world) GetObjectsByDots(dots []*engine.Dot) []interface{} {
	if objects := w.pg.GetObjectsByDots(dots); len(objects) > 0 {
		for _, object := range objects {
			w.event(Event{
				Type:    EventTypeObjectChecked,
				Payload: object,
			})
		}
		return objects
	}
	return nil
}

func (w *world) CreateObject(object interface{}, location engine.Location) error {
	if err := w.pg.CreateObject(object, location); err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return nil
}

func (w *world) CreateObjectAvailableDots(object interface{}, location engine.Location) (engine.Location, *playground.ErrCreateObjectAvailableDots) {
	location, err := w.pg.CreateObjectAvailableDots(object, location)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return location, err
}

func (w *world) DeleteObject(object interface{}, location engine.Location) *playground.ErrDeleteObject {
	err := w.pg.DeleteObject(object, location)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return err
	}
	w.event(Event{
		Type:    EventTypeObjectDelete,
		Payload: object,
	})
	return err
}

func (w *world) UpdateObject(object interface{}, old, new engine.Location) *playground.ErrUpdateObject {
	if err := w.pg.UpdateObject(object, old, new); err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return err
	}
	w.event(Event{
		Type:    EventTypeObjectUpdate,
		Payload: object,
	})
	return nil
}

func (w *world) UpdateObjectAvailableDots(object interface{}, old, new engine.Location) (engine.Location, *playground.ErrUpdateObjectAvailableDots) {
	location, err := w.pg.UpdateObjectAvailableDots(object, old, new)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectUpdate,
		Payload: object,
	})
	return location, err
}

func (w *world) CreateObjectRandomDot(object interface{}) (engine.Location, error) {
	location, err := w.pg.CreateObjectRandomDot(object)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return location, err
}

func (w *world) CreateObjectRandomRect(object interface{}, rw, rh uint8) (engine.Location, error) {
	location, err := w.pg.CreateObjectRandomRect(object, rw, rh)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return location, err
}

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

func (w *world) Size() uint16 {
	return w.pg.Size()
}

func (w *world) Width() uint8 {
	return w.pg.Width()
}

func (w *world) Height() uint8 {
	return w.pg.Height()
}
<commit_msg>Create concurrent sending events to observers in game world<commit_after>package game

import (
	"sync"
	"time"

	"github.com/ivan1993spb/snake-server/engine"
	"github.com/ivan1993spb/snake-server/playground"
)

const worldEventsBufferSize = 512

const worldEventsTimeout = time.Second

const worldEventsNumberLimit = 128

type World interface {
	ObjectExists(object interface{}) bool
	LocationExists(location engine.Location) bool
	EntityExists(object interface{}, location engine.Location) bool
	GetObjectByLocation(location engine.Location) interface{}
	GetObjectByDot(dot *engine.Dot) interface{}
	GetEntityByDot(dot *engine.Dot) (interface{}, engine.Location)
	GetObjectsByDots(dots []*engine.Dot) []interface{}
	CreateObject(object interface{}, location engine.Location) error
	CreateObjectAvailableDots(object interface{}, location engine.Location) (engine.Location, *playground.ErrCreateObjectAvailableDots)
	DeleteObject(object interface{}, location engine.Location) *playground.ErrDeleteObject
	UpdateObject(object interface{}, old, new engine.Location) *playground.ErrUpdateObject
	UpdateObjectAvailableDots(object interface{}, old, new engine.Location) (engine.Location, *playground.ErrUpdateObjectAvailableDots)
	CreateObjectRandomDot(object interface{}) (engine.Location, error)
	CreateObjectRandomRect(object interface{}, rw, rh uint8) (engine.Location, error)
	Navigate(dot *engine.Dot, dir engine.Direction, dis uint8) (*engine.Dot, error)
	Size() uint16
	Width() uint8
	Height() uint8
}

type world struct {
	pg      *playground.Playground
	ch      chan Event
	chs     []chan Event
	chsMux  *sync.RWMutex
	stop    chan struct{}
	timeout time.Duration
}

func newWorld(pg *playground.Playground) *world {
	return &world{
		pg:      pg,
		ch:      make(chan Event, worldEventsBufferSize),
		chs:     make([]chan Event, 0),
		chsMux:  &sync.RWMutex{},
		stop:    make(chan struct{}, 0),
		timeout: worldEventsTimeout,
	}
}

func (w *world) run() {
	go func() {
		for {
			select {
			case event := <-w.ch:
				w.broadcast(event)
			case <-w.stop:
				return
			}
		}
	}()
}

func (w *world) broadcast(event Event) {
	w.chsMux.RLock()

	wg := sync.WaitGroup{}
	wg.Add(len(w.chs))
	for _, ch := range w.chs {
		go func() {
			var timer = time.NewTimer(w.timeout)
			select {
			case ch <- event:
			case <-w.stop:
				return
			case <-timer.C:
			}
			timer.Stop()
			wg.Done()
		}()
	}
	wg.Wait()

	w.chsMux.RUnlock()
}

// TODO: Create reset by count events in awaiting
func (w *world) event(event Event) {
	if len(w.ch) == worldEventsBufferSize {
		// TODO: Create warning messages?
		<-w.ch
	}
	if worldEventsBufferSize == 0 {
		// TODO: Async?
		var timer = time.NewTimer(worldEventsTimeout)
		select {
		case w.ch <- event:
		case <-w.stop:
		case <-timer.C:
		}
		timer.Stop()
	} else {
		w.ch <- event
	}
}

func (w *world) RunObserver(observer interface {
	Run(<-chan Event)
}) {
	ch := make(chan Event, worldEventsBufferSize)

	w.chsMux.Lock()
	w.chs = append(w.chs, ch)
	w.chsMux.Unlock()

	observer.Run(ch)

	w.chsMux.Lock()
	for i := range w.chs {
		if w.chs[i] == ch {
			w.chs = append(w.chs[:i], w.chs[i+1:]...)
			close(ch)
			break
		}
	}
	w.chsMux.Unlock()
}

func (w *world) Stop() {
	close(w.stop)
	close(w.ch)

	w.chsMux.Lock()
	defer w.chsMux.Unlock()

	for _, ch := range w.chs {
		close(ch)
	}

	w.chs = w.chs[:0]
}

func (w *world) ObjectExists(object interface{}) bool {
	return w.pg.ObjectExists(object)
}

func (w *world) LocationExists(location engine.Location) bool {
	return w.pg.LocationExists(location)
}

func (w *world) EntityExists(object interface{}, location engine.Location) bool {
	return w.pg.EntityExists(object, location)
}

func (w *world) GetObjectByLocation(location engine.Location) interface{} {
	if object := w.pg.GetObjectByLocation(location); object != nil {
		w.event(Event{
			Type:    EventTypeObjectChecked,
			Payload: object,
		})
		return object
	}
	return nil

}

func (w *world) GetObjectByDot(dot *engine.Dot) interface{} {
	if object := w.pg.GetObjectByDot(dot); object != nil {
		w.event(Event{
			Type:    EventTypeObjectChecked,
			Payload: object,
		})
		return object
	}
	return nil
}

func (w *world) GetEntityByDot(dot *engine.Dot) (interface{}, engine.Location) {
	if object, location := w.pg.GetEntityByDot(dot); object != nil && !location.Empty() {
		w.event(Event{
			Type:    EventTypeObjectChecked,
			Payload: object,
		})
		return object, location
	}
	return nil, nil
}

func (w *world) GetObjectsByDots(dots []*engine.Dot) []interface{} {
	if objects := w.pg.GetObjectsByDots(dots); len(objects) > 0 {
		for _, object := range objects {
			w.event(Event{
				Type:    EventTypeObjectChecked,
				Payload: object,
			})
		}
		return objects
	}
	return nil
}

func (w *world) CreateObject(object interface{}, location engine.Location) error {
	if err := w.pg.CreateObject(object, location); err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return nil
}

func (w *world) CreateObjectAvailableDots(object interface{}, location engine.Location) (engine.Location, *playground.ErrCreateObjectAvailableDots) {
	location, err := w.pg.CreateObjectAvailableDots(object, location)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return location, err
}

func (w *world) DeleteObject(object interface{}, location engine.Location) *playground.ErrDeleteObject {
	err := w.pg.DeleteObject(object, location)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return err
	}
	w.event(Event{
		Type:    EventTypeObjectDelete,
		Payload: object,
	})
	return err
}

func (w *world) UpdateObject(object interface{}, old, new engine.Location) *playground.ErrUpdateObject {
	if err := w.pg.UpdateObject(object, old, new); err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return err
	}
	w.event(Event{
		Type:    EventTypeObjectUpdate,
		Payload: object,
	})
	return nil
}

func (w *world) UpdateObjectAvailableDots(object interface{}, old, new engine.Location) (engine.Location, *playground.ErrUpdateObjectAvailableDots) {
	location, err := w.pg.UpdateObjectAvailableDots(object, old, new)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectUpdate,
		Payload: object,
	})
	return location, err
}

func (w *world) CreateObjectRandomDot(object interface{}) (engine.Location, error) {
	location, err := w.pg.CreateObjectRandomDot(object)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return location, err
}

func (w *world) CreateObjectRandomRect(object interface{}, rw, rh uint8) (engine.Location, error) {
	location, err := w.pg.CreateObjectRandomRect(object, rw, rh)
	if err != nil {
		w.event(Event{
			Type:    EventTypeError,
			Payload: err,
		})
		return nil, err
	}
	w.event(Event{
		Type:    EventTypeObjectCreate,
		Payload: object,
	})
	return location, err
}

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

func (w *world) Size() uint16 {
	return w.pg.Size()
}

func (w *world) Width() uint8 {
	return w.pg.Width()
}

func (w *world) Height() uint8 {
	return w.pg.Height()
}
<|endoftext|>


<commit_before>package kitsu

import (
	"fmt"
)

// Possible values for User.RatingSystem.
const (
	UserRatingSystemAdvanced = "advanced"
	UserRatingSystemRegular  = "regular"
	UserRatingSystemSimple   = "simple"
)

// Possible values for User.Theme.
const (
	UserThemeLight = "light"
	UserThemeDark  = "dark"
)

// UserService handles communication with the user related methods of the
// Kitsu API.
//
// Kitsu API docs:
// http://docs.kitsu.apiary.io/#reference/users/users
type UserService service

// User represents a Kitsu user.
type User struct {
	ID string `jsonapi:"primary,users"`

	// --- Attributes ---

	// ISO 8601 date and time, e.g. 2017-07-27T22:21:26.824Z
	CreatedAt string `jsonapi:"attr,createdAt,omitempty"`

	// ISO 8601 of last modification, e.g. 2017-07-27T22:47:45.129Z
	UpdatedAt string `jsonapi:"attr,updatedAt,omitempty"`

	// e.g. vikhyat
	Name string `jsonapi:"attr,name,omitempty"`

	PastNames []string `jsonapi:"attr,pastNames,omitempty"`

	// Unique slug used for page URLs, e.g. vikhyat
	Slug string `jsonapi:"attr,slug,omitempty"`

	// Max length of 500 characters, e.g.
	//
	// Co-founder of Hummingbird. Obsessed with Gumi.
	About string `jsonapi:"attr,about,omitempty"`

	// e.g. Seattle, WA
	Location string `jsonapi:"attr,location,omitempty"`

	// e.g. Waifu
	WaifuOrHusbando string `jsonapi:"attr,waifuOrHusbando,omitempty"`

	// e.g. 1716
	followersCount int `jsonapi:"attr,followersCount,omitempty"`

	// e.g. 2031
	followingCount int `jsonapi:"attr,followingCount,omitempty"`

	Birthday string `jsonapi:"attr,birthday,omitempty"`
	Gender   string `jsonapi:"attr,gender,omitempty"`

	CommentsCount       int `jsonapi:"attr,commentsCount,omitempty"`
	FavoritesCount      int `jsonapi:"attr,favoritesCount,omitempty"`
	LikesGivenCount     int `jsonapi:"attr,likesGivenCount,omitempty"`
	ReviewsCount        int `jsonapi:"attr,reviewsCount,omitempty"`
	LikesReceivedCount  int `jsonapi:"attr,likesReceivedCount,omitempty"`
	PostsCount          int `jsonapi:"attr,postsCount,omitempty"`
	RatingsCount        int `jsonapi:"attr,ratingsCount,omitempty"`
	MediaReactionsCount int `jsonapi:"attr,mediaReactionsCount,omitempty"`

	// e.g. 2015-01-30T16:49:35.173Z
	ProExpiresAt string `jsonapi:"attr,proExpiresAt,omitempty"`

	Title string `jsonapi:"attr,title,omitempty"`

	ProfileCompleted bool `jsonapi:"attr,profileCompleted,omitempty"`
	FeedCompleted    bool `jsonapi:"attr,feedCompleted,omitempty"`

	// e.g.
	//
	// "tiny": "https://media.kitsu.io/users/avatars/1/tiny.jpg?1434087646"
	//
	// "small": "https://media.kitsu.io/users/avatars/1/small.jpg?1434087646"
	//
	// "medium": "https://media.kitsu.io/users/avatars/1/medium.jpg?1434087646"
	//
	// "large": "https://media.kitsu.io/users/avatars/1/large.jpg?1434087646"
	//
	// "original": "https://media.kitsu.io/users/avatars/1/original.jpg?1434087646"
	//
	// It may also contain a meta object with additional dimensions objects for
	// each previous Avatar type.
	Avatar     map[string]interface{} `jsonapi:"attr,avatar,omitempty"`
	CoverImage map[string]interface{} `jsonapi:"attr,coverImage,omitempty"`

	// Possible valued described by UserRatingSystem constants.
	RatingSystem string `jsonapi:"attr,ratingSystem,omitempty"`

	// Possible valued described by UserTheme constants.
	Theme string `jsonapi:"attr,theme,omitempty"`

	FacebookID string `jsonapi:"attr,facebookId,omitempty"`

	// --- Relationships ---

	Waifu          *Character      `jsonapi:"relation,waifu,omitempty"`
	LibraryEntries []*LibraryEntry `jsonapi:"relation,libraryEntries,omitempty"`
}

// Show returns details for a specific User by providing the ID of the user
// e.g. 29745.
func (s *UserService) Show(userID string, opts ...URLOption) (*User, *Response, error) {
	u := fmt.Sprintf(defaultAPIVersion+"users/%s", userID)

	req, err := s.client.NewRequest("GET", u, nil, opts...)
	if err != nil {
		return nil, nil, err
	}

	user := new(User)
	resp, err := s.client.Do(req, user)
	if err != nil {
		return nil, resp, err
	}

	return user, resp, nil
}

// List returns a list of Users. Optional parameters can be specified to filter
// the search results and control pagination, sorting etc.
func (s *UserService) List(opts ...URLOption) ([]*User, *Response, error) {
	u := defaultAPIVersion + "users"

	req, err := s.client.NewRequest("GET", u, nil, opts...)
	if err != nil {
		return nil, nil, err
	}

	var users []*User
	resp, err := s.client.Do(req, &users)
	if err != nil {
		return nil, resp, err
	}

	return users, resp, nil
}
<commit_msg>Fix panic caused by unexported User fields<commit_after>package kitsu

import (
	"fmt"
)

// Possible values for User.RatingSystem.
const (
	UserRatingSystemAdvanced = "advanced"
	UserRatingSystemRegular  = "regular"
	UserRatingSystemSimple   = "simple"
)

// Possible values for User.Theme.
const (
	UserThemeLight = "light"
	UserThemeDark  = "dark"
)

// UserService handles communication with the user related methods of the
// Kitsu API.
//
// Kitsu API docs:
// http://docs.kitsu.apiary.io/#reference/users/users
type UserService service

// User represents a Kitsu user.
type User struct {
	ID string `jsonapi:"primary,users"`

	// --- Attributes ---

	// ISO 8601 date and time, e.g. 2017-07-27T22:21:26.824Z
	CreatedAt string `jsonapi:"attr,createdAt,omitempty"`

	// ISO 8601 of last modification, e.g. 2017-07-27T22:47:45.129Z
	UpdatedAt string `jsonapi:"attr,updatedAt,omitempty"`

	// e.g. vikhyat
	Name string `jsonapi:"attr,name,omitempty"`

	PastNames []string `jsonapi:"attr,pastNames,omitempty"`

	// Unique slug used for page URLs, e.g. vikhyat
	Slug string `jsonapi:"attr,slug,omitempty"`

	// Max length of 500 characters, e.g.
	//
	// Co-founder of Hummingbird. Obsessed with Gumi.
	About string `jsonapi:"attr,about,omitempty"`

	// e.g. Seattle, WA
	Location string `jsonapi:"attr,location,omitempty"`

	// e.g. Waifu
	WaifuOrHusbando string `jsonapi:"attr,waifuOrHusbando,omitempty"`

	// e.g. 1716
	FollowersCount int `jsonapi:"attr,followersCount,omitempty"`

	// e.g. 2031
	FollowingCount int `jsonapi:"attr,followingCount,omitempty"`

	Birthday string `jsonapi:"attr,birthday,omitempty"`
	Gender   string `jsonapi:"attr,gender,omitempty"`

	CommentsCount       int `jsonapi:"attr,commentsCount,omitempty"`
	FavoritesCount      int `jsonapi:"attr,favoritesCount,omitempty"`
	LikesGivenCount     int `jsonapi:"attr,likesGivenCount,omitempty"`
	ReviewsCount        int `jsonapi:"attr,reviewsCount,omitempty"`
	LikesReceivedCount  int `jsonapi:"attr,likesReceivedCount,omitempty"`
	PostsCount          int `jsonapi:"attr,postsCount,omitempty"`
	RatingsCount        int `jsonapi:"attr,ratingsCount,omitempty"`
	MediaReactionsCount int `jsonapi:"attr,mediaReactionsCount,omitempty"`

	// e.g. 2015-01-30T16:49:35.173Z
	ProExpiresAt string `jsonapi:"attr,proExpiresAt,omitempty"`

	Title string `jsonapi:"attr,title,omitempty"`

	ProfileCompleted bool `jsonapi:"attr,profileCompleted,omitempty"`
	FeedCompleted    bool `jsonapi:"attr,feedCompleted,omitempty"`

	// e.g.
	//
	// "tiny": "https://media.kitsu.io/users/avatars/1/tiny.jpg?1434087646"
	//
	// "small": "https://media.kitsu.io/users/avatars/1/small.jpg?1434087646"
	//
	// "medium": "https://media.kitsu.io/users/avatars/1/medium.jpg?1434087646"
	//
	// "large": "https://media.kitsu.io/users/avatars/1/large.jpg?1434087646"
	//
	// "original": "https://media.kitsu.io/users/avatars/1/original.jpg?1434087646"
	//
	// It may also contain a meta object with additional dimensions objects for
	// each previous Avatar type.
	Avatar     map[string]interface{} `jsonapi:"attr,avatar,omitempty"`
	CoverImage map[string]interface{} `jsonapi:"attr,coverImage,omitempty"`

	// Possible valued described by UserRatingSystem constants.
	RatingSystem string `jsonapi:"attr,ratingSystem,omitempty"`

	// Possible valued described by UserTheme constants.
	Theme string `jsonapi:"attr,theme,omitempty"`

	FacebookID string `jsonapi:"attr,facebookId,omitempty"`

	// --- Relationships ---

	Waifu          *Character      `jsonapi:"relation,waifu,omitempty"`
	LibraryEntries []*LibraryEntry `jsonapi:"relation,libraryEntries,omitempty"`
}

// Show returns details for a specific User by providing the ID of the user
// e.g. 29745.
func (s *UserService) Show(userID string, opts ...URLOption) (*User, *Response, error) {
	u := fmt.Sprintf(defaultAPIVersion+"users/%s", userID)

	req, err := s.client.NewRequest("GET", u, nil, opts...)
	if err != nil {
		return nil, nil, err
	}

	user := new(User)
	resp, err := s.client.Do(req, user)
	if err != nil {
		return nil, resp, err
	}

	return user, resp, nil
}

// List returns a list of Users. Optional parameters can be specified to filter
// the search results and control pagination, sorting etc.
func (s *UserService) List(opts ...URLOption) ([]*User, *Response, error) {
	u := defaultAPIVersion + "users"

	req, err := s.client.NewRequest("GET", u, nil, opts...)
	if err != nil {
		return nil, nil, err
	}

	var users []*User
	resp, err := s.client.Do(req, &users)
	if err != nil {
		return nil, resp, err
	}

	return users, resp, nil
}
<|endoftext|>