<commit_before>// Copyright 2016 ~ 2017 AlexStocks(https://github.com/AlexStocks).
// All rights reserved.  Use of this source code is
// governed by Apache License 2.0.

// 2017-04-02 02:04
// package gxelasticsearch provides a Elasticsearch driver
package gxelasticsearch

import (
	"context"
	"encoding/json"
	"fmt"
)

import (
	"github.com/AlexStocks/goext/log"
	// Log "github.com/AlexStocks/log4go"
	"github.com/pkg/errors"
	es "gopkg.in/olivere/elastic.v3"
)

type EsClient struct {
	*es.Client
	bulk *es.BulkService
}

func CreateEsClient(hosts []string) (EsClient, error) {
	var esClient EsClient
	// Create a client
	// snif的作用是根据部分url获取整个集群的urls
	client, err := es.NewClient(es.SetURL(hosts[:]...), es.SetSniff(true), es.SetHealthcheck(true))
	if err == nil {
		esClient.Client = client
	}

	return esClient, err
}

// https://github.com/olivere/elastic/issues/457
func buildEsIndexSettings(shardNum int32, replicaNum int32, refreshInterval int32) string {
	return fmt.Sprintf(`{
		"settings" : {
			"number_of_shards": %d,
			"number_of_replicas": %d,
			"refresh_interval": "%ds"
		}
	}`, shardNum, replicaNum, refreshInterval)
}

func (ec EsClient) CreateEsIndex(index string, shardNum int32, replicaNum int32, refreshInterval int32) error {
	var (
		err    error
		exists bool
		body   string
		ctx    context.Context
	)

	ctx = context.Background()
	exists, err = ec.IndexExists(index).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "CreateRcIndex(index:%s, shardNum:%s, replicaNum:%d, refreshInterval:%d)",
			index, shardNum, replicaNum, refreshInterval)
	}
	if exists {
		return nil
	}

	body = buildEsIndexSettings(shardNum, replicaNum, refreshInterval)
	_, err = ec.CreateIndex(index).BodyString(body).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "CreateEsIndex(body:%s)", body)
	}

	return nil
}

func (ec EsClient) DeleteEsIndex(index string) error {
	var (
		err error
		ctx context.Context
	)

	ctx = context.Background()
	_, err = ec.DeleteIndex(index).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "DeleteEsIndex(index:%s)", index)
	}

	return nil
}

// InsertWithDocId 插入@msg
// !!! 如果@msg的类型是string 或者 []byte，则被当做Json String类型直接存进去
func (ec EsClient) Insert(index string, typ string, msg interface{}) error {
	var (
		err      error
		ok       bool
		msgBytes []byte
		ctx      context.Context
	)

	// https://github.com/olivere/elastic/issues/127
	// Elasticsearch can create an identifier for you, automatically.
	// _, err = ec.Index().Index(index).Type(typ).Id(1).BodyJson(msg).Do()
	ctx = context.Background()
	switch msg.(type) {
	case string:
		_, err = ec.Index().Index(index).Type(typ).BodyString(msg.(string)).DoC(ctx)
		if err != nil {
			return errors.Wrapf(err, "Insert(index:%s, type:%s, msg:%s)", index, typ, msg)
		}

	default:
		if msgBytes, ok = msg.([]byte); ok {
			_, err = ec.Index().Index(index).Type(typ).BodyString(string(msgBytes)).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "Insert(index:%s, type:%s, msg:%s)", index, typ, (string)(msgBytes))
			}
		} else {
			_, err = ec.Index().Index(index).Type(typ).BodyJson(msg).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "Insert(index:%s, type:%s, msg:%#v)", index, typ, msg)
			}
		}
	}

	return nil
}

// InsertWithDocId 插入@msg时候指定@docID
// !!! 如果@msg的类型是string 或者 []byte，则被当做Json String类型直接存进去
func (ec EsClient) InsertWithDocId(index string, typ string, docID string, msg interface{}) error {
	var (
		err      error
		ok       bool
		msgBytes []byte
		ctx      context.Context
	)

	ctx = context.Background()
	switch msg.(type) {
	case string:
		_, err = ec.Index().Index(index).Type(typ).Id(docID).BodyString(msg.(string)).DoC(ctx)
		if err != nil {
			return errors.Wrapf(err, "InsertWithDocId(index:%s, type:%s, docID:%s, msg:%s)", index, typ, docID, msg)
		}

	default:
		if msgBytes, ok = msg.([]byte); ok {
			_, err = ec.Index().Index(index).Type(typ).Id(docID).BodyString(string(msgBytes)).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "InsertWithDocId(index:%s, type:%s, docID:%s, msg:%s)", index, typ, docID, (string)(msgBytes))
			}
		} else {
			_, err = ec.Index().Index(index).Type(typ).Id(docID).BodyJson(msg).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "InsertWithDocId(index:%s, type:%s, docID:%s, msg:%#v)", index, typ, docID, msg)
			}
		}
	}

	return nil
}

// BulkInsert 批量插入@arr
// !!! 如果@arr[0]的类型是string 或者 []byte，则被当做Json String类型直接存进去
// https://www.elastic.co/guide/en/elasticsearch/guide/current/bulk.html
// A good place to start is with batches of 1,000 to 5,000 documents
func (ec EsClient) BulkInsert(index string, typ string, arr []interface{}) error {
	var (
		err  error
		ctx  context.Context
		bulk *es.BulkService
		rsp  *es.BulkResponse
	)

	if ec.bulk == nil {
		ec.bulk = ec.Bulk()
	}

	bulk = ec.bulk.Index(index).Type(typ)
	for _, e := range arr {
		switch e.(type) {
		case string:
			data := ([]byte)(e.(string))
			bulk.Add(es.NewBulkIndexRequest().Doc((*json.RawMessage)(&data)))
		default:
			if data, ok := e.([]byte); ok {
				bulk.Add(es.NewBulkIndexRequest().Doc((*json.RawMessage)(&data)))
			} else {
				bulk.Add(es.NewBulkIndexRequest().Doc(e))
				// bulk.Add(es.NewBulkIndexRequest().Index("1").Doc(e))
			}
		}
	}
	if bulk.NumberOfActions() <= 0 {
		return fmt.Errorf("bulk.NumberOfActions() = %d", bulk.NumberOfActions())
	}

	ctx = context.Background()
	rsp, err = bulk.DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "BulkInsert(@arr len:%d)", len(arr))
	}
	if rsp.Errors {
		// for i, f := range rsp.Failed() {
		// 	Log.Warn("index:%d, fail:%s", i, gxlog.PrettyStruct(f))
		// }
		return fmt.Errorf("BulkInsert(@arr len:%d), failed number:%#v, first fail{reason:%#v, fail detail:%#v}",
			len(arr), len(rsp.Failed()), gxlog.PrettyString(rsp.Failed()[0]))
	}

	return nil
}

// Search
// Waiting for es5.x's future sql feature
<commit_msg>time stamp elasticsearch mapping for kibana<commit_after>// Copyright 2016 ~ 2017 AlexStocks(https://github.com/AlexStocks).
// All rights reserved.  Use of this source code is
// governed by Apache License 2.0.

// 2017-04-02 02:04
// package gxelasticsearch provides a Elasticsearch driver
package gxelasticsearch

import (
	"context"
	"encoding/json"
	"fmt"
)

import (
	"github.com/AlexStocks/goext/log"
	// Log "github.com/AlexStocks/log4go"
	"github.com/pkg/errors"
	es "gopkg.in/olivere/elastic.v3"
)

type EsClient struct {
	*es.Client
	bulk *es.BulkService
}

func CreateEsClient(hosts []string) (EsClient, error) {
	var esClient EsClient
	// Create a client
	// snif的作用是根据部分url获取整个集群的urls
	client, err := es.NewClient(es.SetURL(hosts[:]...), es.SetSniff(true), es.SetHealthcheck(true))
	if err == nil {
		esClient.Client = client
	}

	return esClient, err
}

// https://github.com/olivere/elastic/issues/457
func buildEsIndexSettings(shardNum int32, replicaNum int32, refreshInterval int32) string {
	return fmt.Sprintf(`{
		"settings" : {
			"number_of_shards": %d,
			"number_of_replicas": %d,
			"refresh_interval": "%ds"
		}
	}`, shardNum, replicaNum, refreshInterval)
}

func buildEsIndexSettingsWithTimestamp(shardNum int32, replicaNum int32, refreshInterval int32,
	indexType string, timestampField string, timestampFormat string) string {

	return fmt.Sprintf(`{
		"settings" : {
			"number_of_shards": %d,
			"number_of_replicas": %d,
			"refresh_interval": "%ds"
		},
		"mappings" : {
	    "%s" : {
				"properties" : {
					"%s" : {
						"type":"date",
						"format":"%s"
					}
				}
	    }
		}
	}`, shardNum, replicaNum, refreshInterval, indexType, timestampField, timestampFormat)
}

func (ec EsClient) CreateEsIndex(index string, shardNum int32, replicaNum int32, refreshInterval int32) error {
	var (
		err    error
		exists bool
		body   string
		ctx    context.Context
	)

	ctx = context.Background()
	exists, err = ec.IndexExists(index).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "CreateRcIndex(index:%s, shardNum:%s, replicaNum:%d, refreshInterval:%d)",
			index, shardNum, replicaNum, refreshInterval)
	}
	if exists {
		return nil
	}

	body = buildEsIndexSettings(shardNum, replicaNum, refreshInterval)
	_, err = ec.CreateIndex(index).BodyString(body).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "CreateEsIndex(body:%s)", body)
	}

	return nil
}

func (ec EsClient) CreateEsIndexWithTimestamp(index string, shardNum int32, replicaNum int32, refreshInterval int32,
	indexType string, timestampField string, timestampFormat string) error {
	var (
		err    error
		exists bool
		body   string
		ctx    context.Context
	)

	ctx = context.Background()
	exists, err = ec.IndexExists(index).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "CreateRcIndex(index:%s, shardNum:%s, replicaNum:%d, refreshInterval:%d)",
			index, shardNum, replicaNum, refreshInterval)
	}
	if exists {
		return nil
	}

	body = buildEsIndexSettingsWithTimestamp(shardNum, replicaNum, refreshInterval, indexType, timestampField, timestampFormat)
	_, err = ec.CreateIndex(index).BodyString(body).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "CreateEsIndex(body:%s)", body)
	}

	return nil
}

func (ec EsClient) DeleteEsIndex(index string) error {
	var (
		err error
		ctx context.Context
	)

	ctx = context.Background()
	_, err = ec.DeleteIndex(index).DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "DeleteEsIndex(index:%s)", index)
	}

	return nil
}

// InsertWithDocId 插入@msg
// !!! 如果@msg的类型是string 或者 []byte，则被当做Json String类型直接存进去
func (ec EsClient) Insert(index string, typ string, msg interface{}) error {
	var (
		err      error
		ok       bool
		msgBytes []byte
		ctx      context.Context
	)

	// https://github.com/olivere/elastic/issues/127
	// Elasticsearch can create an identifier for you, automatically.
	// _, err = ec.Index().Index(index).Type(typ).Id(1).BodyJson(msg).Do()
	ctx = context.Background()
	switch msg.(type) {
	case string:
		_, err = ec.Index().Index(index).Type(typ).BodyString(msg.(string)).DoC(ctx)
		if err != nil {
			return errors.Wrapf(err, "Insert(index:%s, type:%s, msg:%s)", index, typ, msg)
		}

	default:
		if msgBytes, ok = msg.([]byte); ok {
			_, err = ec.Index().Index(index).Type(typ).BodyString(string(msgBytes)).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "Insert(index:%s, type:%s, msg:%s)", index, typ, (string)(msgBytes))
			}
		} else {
			_, err = ec.Index().Index(index).Type(typ).BodyJson(msg).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "Insert(index:%s, type:%s, msg:%#v)", index, typ, msg)
			}
		}
	}

	return nil
}

// InsertWithDocId 插入@msg时候指定@docID
// !!! 如果@msg的类型是string 或者 []byte，则被当做Json String类型直接存进去
func (ec EsClient) InsertWithDocId(index string, typ string, docID string, msg interface{}) error {
	var (
		err      error
		ok       bool
		msgBytes []byte
		ctx      context.Context
	)

	ctx = context.Background()
	switch msg.(type) {
	case string:
		_, err = ec.Index().Index(index).Type(typ).Id(docID).BodyString(msg.(string)).DoC(ctx)
		if err != nil {
			return errors.Wrapf(err, "InsertWithDocId(index:%s, type:%s, docID:%s, msg:%s)", index, typ, docID, msg)
		}

	default:
		if msgBytes, ok = msg.([]byte); ok {
			_, err = ec.Index().Index(index).Type(typ).Id(docID).BodyString(string(msgBytes)).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "InsertWithDocId(index:%s, type:%s, docID:%s, msg:%s)", index, typ, docID, (string)(msgBytes))
			}
		} else {
			_, err = ec.Index().Index(index).Type(typ).Id(docID).BodyJson(msg).DoC(ctx)
			if err != nil {
				return errors.Wrapf(err, "InsertWithDocId(index:%s, type:%s, docID:%s, msg:%#v)", index, typ, docID, msg)
			}
		}
	}

	return nil
}

// BulkInsert 批量插入@arr
// !!! 如果@arr[0]的类型是string 或者 []byte，则被当做Json String类型直接存进去
// https://www.elastic.co/guide/en/elasticsearch/guide/current/bulk.html
// A good place to start is with batches of 1,000 to 5,000 documents
func (ec EsClient) BulkInsert(index string, typ string, arr []interface{}) error {
	var (
		err  error
		ctx  context.Context
		bulk *es.BulkService
		rsp  *es.BulkResponse
	)

	if ec.bulk == nil {
		ec.bulk = ec.Bulk()
	}

	bulk = ec.bulk.Index(index).Type(typ)
	for _, e := range arr {
		switch e.(type) {
		case string:
			data := ([]byte)(e.(string))
			bulk.Add(es.NewBulkIndexRequest().Doc((*json.RawMessage)(&data)))
		default:
			if data, ok := e.([]byte); ok {
				bulk.Add(es.NewBulkIndexRequest().Doc((*json.RawMessage)(&data)))
			} else {
				bulk.Add(es.NewBulkIndexRequest().Doc(e))
				// bulk.Add(es.NewBulkIndexRequest().Index("1").Doc(e))
			}
		}
	}
	if bulk.NumberOfActions() <= 0 {
		return fmt.Errorf("bulk.NumberOfActions() = %d", bulk.NumberOfActions())
	}

	ctx = context.Background()
	rsp, err = bulk.DoC(ctx)
	if err != nil {
		return errors.Wrapf(err, "BulkInsert(@arr len:%d)", len(arr))
	}
	if rsp.Errors {
		// for i, f := range rsp.Failed() {
		// 	Log.Warn("index:%d, fail:%s", i, gxlog.PrettyStruct(f))
		// }
		return fmt.Errorf("BulkInsert(@arr len:%d), failed number:%#v, first fail{reason:%#v, fail detail:%#v}",
			len(arr), len(rsp.Failed()), gxlog.PrettyString(rsp.Failed()[0]))
	}

	return nil
}

// Search
// Waiting for es5.x's future sql feature
<|endoftext|>


<commit_before>package drivers

import (
	"io"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/migration"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/instancewriter"
)

type mock struct {
	common
}

// load is used to run one-time action per-driver rather than per-pool.
func (d *mock) load() error {
	return nil
}

// Info returns info about the driver and its environment.
func (d *mock) Info() Info {
	return Info{
		Name:              "mock",
		Version:           "1",
		OptimizedImages:   false,
		PreservesInodes:   false,
		Remote:            d.isRemote(),
		VolumeTypes:       []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
		BlockBacking:      false,
		RunningCopyFreeze: true,
		DirectIO:          true,
		MountedRoot:       true,
	}
}

func (d *mock) Create() error {
	return nil
}

func (d *mock) Delete(op *operations.Operation) error {
	return nil
}

// Validate checks that all provide keys are supported and that no conflicting or missing configuration is present.
func (d *mock) Validate(config map[string]string) error {
	return d.validatePool(config, nil)
}

// Update applies any driver changes required from a configuration change.
func (d *mock) Update(changedConfig map[string]string) error {
	return nil
}

// Mount mounts the storage pool.
func (d *mock) Mount() (bool, error) {
	return true, nil
}

// Unmount unmounts the storage pool.
func (d *mock) Unmount() (bool, error) {
	return true, nil
}

// GetResources returns the pool resource usage information.
func (d *mock) GetResources() (*api.ResourcesStoragePool, error) {
	return nil, nil
}

// CreateVolume creates an empty volume and can optionally fill it by executing the supplied filler function.
func (d *mock) CreateVolume(vol Volume, filler *VolumeFiller, op *operations.Operation) error {
	return nil
}

// CreateVolumeFromBackup restores a backup tarball onto the storage device.
func (d *mock) CreateVolumeFromBackup(vol Volume, srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (VolumePostHook, revert.Hook, error) {
	return nil, nil, nil
}

// CreateVolumeFromCopy provides same-pool volume copying functionality.
func (d *mock) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op *operations.Operation) error {
	return nil
}

// CreateVolumeFromMigration creates a volume being sent via a migration.
func (d *mock) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op *operations.Operation) error {
	return nil
}

// RefreshVolume provides same-pool volume and specific snapshots syncing functionality.
func (d *mock) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error {
	return nil
}

// DeleteVolume deletes a volume of the storage device. If any snapshots of the volume remain then this function
// will return an error.
func (d *mock) DeleteVolume(vol Volume, op *operations.Operation) error {
	return nil
}

// HasVolume indicates whether a specific volume exists on the storage pool.
func (d *mock) HasVolume(vol Volume) bool {
	return true
}

// ValidateVolume validates the supplied volume config. Optionally removes invalid keys from the volume's config.
func (d *mock) ValidateVolume(vol Volume, removeUnknownKeys bool) error {
	return nil
}

// UpdateVolume applies config changes to the volume.
func (d *mock) UpdateVolume(vol Volume, changedConfig map[string]string) error {
	if vol.contentType != ContentTypeFS {
		return ErrNotSupported
	}

	if _, changed := changedConfig["size"]; changed {
		err := d.SetVolumeQuota(vol, changedConfig["size"], nil)
		if err != nil {
			return err
		}
	}

	return nil
}

// GetVolumeUsage returns the disk space used by the volume.
func (d *mock) GetVolumeUsage(vol Volume) (int64, error) {
	return 0, nil
}

// SetVolumeQuota applies a size limit on volume.
func (d *mock) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, op *operations.Operation) error {
	return nil
}

// GetVolumeDiskPath returns the location of a disk volume.
func (d *mock) GetVolumeDiskPath(vol Volume) (string, error) {
	return "", nil
}

// MountVolume simulates mounting a volume.
func (d *mock) MountVolume(vol Volume, op *operations.Operation) error {
	return nil
}

// UnmountVolume simulates unmounting a volume. As dir driver doesn't have volumes to unmount it
// returns false indicating the volume was already unmounted.
func (d *mock) UnmountVolume(vol Volume, keepBlockDev bool, op *operations.Operation) (bool, error) {
	return false, nil
}

// RenameVolume renames a volume and its snapshots.
func (d *mock) RenameVolume(vol Volume, newVolName string, op *operations.Operation) error {
	return nil
}

// MigrateVolume sends a volume for migration.
func (d *mock) MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs *migration.VolumeSourceArgs, op *operations.Operation) error {
	return nil
}

// BackupVolume copies a volume (and optionally its snapshots) to a specified target path.
// This driver does not support optimized backups.
func (d *mock) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWriter, optimized bool, snapshots []string, op *operations.Operation) error {
	return nil
}

// CreateVolumeSnapshot creates a snapshot of a volume.
func (d *mock) CreateVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	return nil
}

// DeleteVolumeSnapshot removes a snapshot from the storage device. The volName and snapshotName
// must be bare names and should not be in the format "volume/snapshot".
func (d *mock) DeleteVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	return nil
}

// MountVolumeSnapshot sets up a read-only mount on top of the snapshot to avoid accidental modifications.
func (d *mock) MountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	return true, nil
}

// UnmountVolumeSnapshot removes the read-only mount placed on top of a snapshot.
func (d *mock) UnmountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	return true, nil
}

// VolumeSnapshots returns a list of snapshots for the volume (in no particular order).
func (d *mock) VolumeSnapshots(vol Volume, op *operations.Operation) ([]string, error) {
	return nil, nil
}

// RestoreVolume restores a volume from a snapshot.
func (d *mock) RestoreVolume(vol Volume, snapshotName string, op *operations.Operation) error {
	return nil
}

// RenameVolumeSnapshot renames a volume snapshot.
func (d *mock) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
	return nil
}
<commit_msg>lxd/storage/drivers/driver/btrfs/mock: d.SetVolumeQuota usage<commit_after>package drivers

import (
	"io"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/migration"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/instancewriter"
)

type mock struct {
	common
}

// load is used to run one-time action per-driver rather than per-pool.
func (d *mock) load() error {
	return nil
}

// Info returns info about the driver and its environment.
func (d *mock) Info() Info {
	return Info{
		Name:              "mock",
		Version:           "1",
		OptimizedImages:   false,
		PreservesInodes:   false,
		Remote:            d.isRemote(),
		VolumeTypes:       []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
		BlockBacking:      false,
		RunningCopyFreeze: true,
		DirectIO:          true,
		MountedRoot:       true,
	}
}

func (d *mock) Create() error {
	return nil
}

func (d *mock) Delete(op *operations.Operation) error {
	return nil
}

// Validate checks that all provide keys are supported and that no conflicting or missing configuration is present.
func (d *mock) Validate(config map[string]string) error {
	return d.validatePool(config, nil)
}

// Update applies any driver changes required from a configuration change.
func (d *mock) Update(changedConfig map[string]string) error {
	return nil
}

// Mount mounts the storage pool.
func (d *mock) Mount() (bool, error) {
	return true, nil
}

// Unmount unmounts the storage pool.
func (d *mock) Unmount() (bool, error) {
	return true, nil
}

// GetResources returns the pool resource usage information.
func (d *mock) GetResources() (*api.ResourcesStoragePool, error) {
	return nil, nil
}

// CreateVolume creates an empty volume and can optionally fill it by executing the supplied filler function.
func (d *mock) CreateVolume(vol Volume, filler *VolumeFiller, op *operations.Operation) error {
	return nil
}

// CreateVolumeFromBackup restores a backup tarball onto the storage device.
func (d *mock) CreateVolumeFromBackup(vol Volume, srcBackup backup.Info, srcData io.ReadSeeker, op *operations.Operation) (VolumePostHook, revert.Hook, error) {
	return nil, nil, nil
}

// CreateVolumeFromCopy provides same-pool volume copying functionality.
func (d *mock) CreateVolumeFromCopy(vol Volume, srcVol Volume, copySnapshots bool, op *operations.Operation) error {
	return nil
}

// CreateVolumeFromMigration creates a volume being sent via a migration.
func (d *mock) CreateVolumeFromMigration(vol Volume, conn io.ReadWriteCloser, volTargetArgs migration.VolumeTargetArgs, preFiller *VolumeFiller, op *operations.Operation) error {
	return nil
}

// RefreshVolume provides same-pool volume and specific snapshots syncing functionality.
func (d *mock) RefreshVolume(vol Volume, srcVol Volume, srcSnapshots []Volume, op *operations.Operation) error {
	return nil
}

// DeleteVolume deletes a volume of the storage device. If any snapshots of the volume remain then this function
// will return an error.
func (d *mock) DeleteVolume(vol Volume, op *operations.Operation) error {
	return nil
}

// HasVolume indicates whether a specific volume exists on the storage pool.
func (d *mock) HasVolume(vol Volume) bool {
	return true
}

// ValidateVolume validates the supplied volume config. Optionally removes invalid keys from the volume's config.
func (d *mock) ValidateVolume(vol Volume, removeUnknownKeys bool) error {
	return nil
}

// UpdateVolume applies config changes to the volume.
func (d *mock) UpdateVolume(vol Volume, changedConfig map[string]string) error {
	if vol.contentType != ContentTypeFS {
		return ErrNotSupported
	}

	if _, changed := changedConfig["size"]; changed {
		err := d.SetVolumeQuota(vol, changedConfig["size"], false, nil)
		if err != nil {
			return err
		}
	}

	return nil
}

// GetVolumeUsage returns the disk space used by the volume.
func (d *mock) GetVolumeUsage(vol Volume) (int64, error) {
	return 0, nil
}

// SetVolumeQuota applies a size limit on volume.
func (d *mock) SetVolumeQuota(vol Volume, size string, allowUnsafeResize bool, op *operations.Operation) error {
	return nil
}

// GetVolumeDiskPath returns the location of a disk volume.
func (d *mock) GetVolumeDiskPath(vol Volume) (string, error) {
	return "", nil
}

// MountVolume simulates mounting a volume.
func (d *mock) MountVolume(vol Volume, op *operations.Operation) error {
	return nil
}

// UnmountVolume simulates unmounting a volume. As dir driver doesn't have volumes to unmount it
// returns false indicating the volume was already unmounted.
func (d *mock) UnmountVolume(vol Volume, keepBlockDev bool, op *operations.Operation) (bool, error) {
	return false, nil
}

// RenameVolume renames a volume and its snapshots.
func (d *mock) RenameVolume(vol Volume, newVolName string, op *operations.Operation) error {
	return nil
}

// MigrateVolume sends a volume for migration.
func (d *mock) MigrateVolume(vol Volume, conn io.ReadWriteCloser, volSrcArgs *migration.VolumeSourceArgs, op *operations.Operation) error {
	return nil
}

// BackupVolume copies a volume (and optionally its snapshots) to a specified target path.
// This driver does not support optimized backups.
func (d *mock) BackupVolume(vol Volume, tarWriter *instancewriter.InstanceTarWriter, optimized bool, snapshots []string, op *operations.Operation) error {
	return nil
}

// CreateVolumeSnapshot creates a snapshot of a volume.
func (d *mock) CreateVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	return nil
}

// DeleteVolumeSnapshot removes a snapshot from the storage device. The volName and snapshotName
// must be bare names and should not be in the format "volume/snapshot".
func (d *mock) DeleteVolumeSnapshot(snapVol Volume, op *operations.Operation) error {
	return nil
}

// MountVolumeSnapshot sets up a read-only mount on top of the snapshot to avoid accidental modifications.
func (d *mock) MountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	return true, nil
}

// UnmountVolumeSnapshot removes the read-only mount placed on top of a snapshot.
func (d *mock) UnmountVolumeSnapshot(snapVol Volume, op *operations.Operation) (bool, error) {
	return true, nil
}

// VolumeSnapshots returns a list of snapshots for the volume (in no particular order).
func (d *mock) VolumeSnapshots(vol Volume, op *operations.Operation) ([]string, error) {
	return nil, nil
}

// RestoreVolume restores a volume from a snapshot.
func (d *mock) RestoreVolume(vol Volume, snapshotName string, op *operations.Operation) error {
	return nil
}

// RenameVolumeSnapshot renames a volume snapshot.
func (d *mock) RenameVolumeSnapshot(snapVol Volume, newSnapshotName string, op *operations.Operation) error {
	return nil
}
<|endoftext|>


<commit_before>package s3fs_test

import (
	"errors"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	fs "github.com/ungerik/go-fs"
	"github.com/ungerik/go-fs/s3fs"
)

var s3 *s3fs.S3FileSystem
var bucketName string

type defaultPathTestCase struct {
	input string
	want  string
}

// NOTE: For most of these you need to have AWS credentials defined somewhere.
// Read more at: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html

func TestMain(m *testing.M) {
	bucketName = os.Getenv("S3_BUCKET_NAME")
	if bucketName == "" {
		panic(errors.New("You need to set S3_BUCKET_NAME environment variable"))
	}
	s3 = createS3FSInstance()

	retCode := m.Run()
	os.Exit(retCode)
}

func createS3FSInstance() *s3fs.S3FileSystem {
	r, err := s3fs.RegionFromString(os.Getenv("S3_REGION"))
	if err != nil {
		panic(err)
	}
	return s3fs.New(os.Getenv("S3_BUCKET_NAME"), r, time.Second*10)
}

func TestNew(t *testing.T) {
	fs := createS3FSInstance()
	assert.NotNil(t, fs)
}

func TestClose(t *testing.T) {
	if err := s3.Close(); err != nil {
		t.Error(err)
	}

	assert.NotContains(t, fs.Registry, s3.Prefix())
	// We need to create the instance again for other tests
	s3 = createS3FSInstance()
}

func TestFile(t *testing.T) {
	f := s3.File("test/doc.pdf")
	assert.NotNil(t, f)
	assert.NotEmpty(t, f)
	assert.Equal(t, "/test/doc.pdf", f.Path())
	assert.Equal(t, "doc.pdf", f.Name())
	assert.Equal(t, fmt.Sprintf("s3://%s/test/doc.pdf", bucketName), f.URL())
}

func TestJoinCleanFile(t *testing.T) {
	testCases := []struct {
		input string
		want  fs.File
	}{
		{fmt.Sprintf("s3://%s/test.pdf", bucketName), fs.File(fmt.Sprintf("s3://%s/test.pdf", bucketName))},
		{"test.pdf", fs.File(fmt.Sprintf("s3://%s/test.pdf", bucketName))},
		{"/test.pdf", fs.File(fmt.Sprintf("s3://%s/test.pdf", bucketName))},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.JoinCleanFile(tc.input))
	}
}

func TestURL(t *testing.T) {
	assert.Equal(t, fmt.Sprintf("s3://%s/test/test.pdf", bucketName), s3.URL("/test/test.pdf"))
}

func TestJoinCleanPath(t *testing.T) {
	testCases := []defaultPathTestCase{
		{fmt.Sprintf("s3://%s/test.pdf", bucketName), "/test.pdf"},
		{"test.pdf", "/test.pdf"},
		{"/test.pdf", "/test.pdf"},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.JoinCleanPath(tc.input))
	}
}

func TestSplitPath(t *testing.T) {
	testCases := []struct {
		input string
		want  []string
	}{
		{fmt.Sprintf("s3://%s/test/test.pdf", bucketName), []string{"test", "test.pdf"}},
		{"/test/test.pdf", []string{"test", "test.pdf"}},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.SplitPath(tc.input))
	}
}

func TestDirAndName(t *testing.T) {
	testCases := []struct {
		input string
		want  [2]string
	}{
		{fmt.Sprintf("s3://%s/test/test.pdf", bucketName), [2]string{fmt.Sprintf("s3://%s/test", bucketName), "test.pdf"}},
		{"/test/test.pdf", [2]string{"/test", "test.pdf"}},
	}
	for _, tc := range testCases {
		dir, name := s3.DirAndName(tc.input)
		res := [2]string{dir, name}
		assert.Equal(t, tc.want, res)
	}
}

func TestIsAbsPath(t *testing.T) {
	assert.True(t, s3.IsAbsPath("/test/test.pdf"))
	assert.False(t, s3.IsAbsPath("test.df"))
}

func TestAbsPath(t *testing.T) {
	testCases := []defaultPathTestCase{
		{fmt.Sprintf("s3://%s/test/test.pdf", bucketName), fmt.Sprintf("s3://%s/test/test.pdf", bucketName)},
		{"test.pdf", "/test.pdf"},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.AbsPath(tc.input))
	}
}

func TestRoot(t *testing.T) {
	expectedFile := s3.File("/")
	actualFile := s3.Root()
	assert.Equal(t, expectedFile, actualFile)
}

func TestStat(t *testing.T) {
	i := s3.Stat("pdf_input/doc.pdf")
	assert.NotEmpty(t, i)
}

func TestStatNotExists(t *testing.T) {
	i := s3.Stat("DOESNOTEXIST/")
	assert.Equal(t, false, i.Exists)
}

func TestListDir(t *testing.T) {
	var fileInfos []fs.FileInfo
	err := s3.ListDirInfo("pdf_input/", func(f fs.File, fi fs.FileInfo) error {
		fileInfos = append(fileInfos, fi)
		t.Log(string(f))
		return nil
	}, nil)
	assert.NoError(t, err)
	assert.NotEmpty(t, fileInfos)
}
<commit_msg>Add more tests to the s3fs test suite<commit_after>package s3fs_test

import (
	"errors"
	"fmt"
	"os"
	"path"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	fs "github.com/ungerik/go-fs"
	"github.com/ungerik/go-fs/s3fs"
)

// Problems with directories:
// The API in general is a bit incosistent here. S3 requires trailing slashes
// when creating, changing, deleting directories, but treats them just like any
// other object and therefore doesn't return their keys with a trailing slash.
// This means that some tests require us to add the trailing slash.
//
// We could actually just define directories like all the other objects, but
// that would make it more difficult to determine which objects are
// "directories" and which aren't.
var directories = []string{"test", "test1", "test/test2"}
var files = []string{"test.txt", "test/test.txt", "test/test2/text.txt"}

var testData = []byte("TEST 123")

var s3 *s3fs.S3FileSystem
var bucketName string

type defaultPathTestCase struct {
	input string
	want  string
}

// NOTE: For most of these you need to have AWS credentials defined somewhere.
// Read more at: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html

func TestMain(m *testing.M) {
	bucketName = os.Getenv("S3_BUCKET_NAME")
	if bucketName == "" {
		panic(errors.New("You need to set S3_BUCKET_NAME environment variable"))
	}
	s3 = createS3FSInstance()

	retCode := m.Run()
	os.Exit(retCode)
}

func createS3FSInstance() *s3fs.S3FileSystem {
	r, err := s3fs.RegionFromString(os.Getenv("S3_REGION"))
	if err != nil {
		panic(err)
	}
	return s3fs.New(os.Getenv("S3_BUCKET_NAME"), r, time.Second*10)
}

func TestNew(t *testing.T) {
	fs := createS3FSInstance()
	assert.NotNil(t, fs)
}

func TestClose(t *testing.T) {
	if err := s3.Close(); err != nil {
		t.Error(err)
	}

	assert.NotContains(t, fs.Registry, s3.Prefix())
	// We need to create the instance again for other tests
	s3 = createS3FSInstance()
}

func TestRoot(t *testing.T) {
	assert.Equal(t, fmt.Sprintf("%s%s/", s3fs.Prefix, bucketName), string(s3.Root()))
}

func TestID(t *testing.T) {
	id, err := s3.ID()
	if err != nil {
		t.Error(err)
	}
	assert.Equal(t, bucketName, id)
}

func TestPrefix(t *testing.T) {
	assert.Equal(t, fmt.Sprintf("%s%s", s3fs.Prefix, bucketName), s3.Prefix())
}

func TestFile(t *testing.T) {
	testCases := []struct {
		filePath string
		vals     []string
	}{
		{"test/doc.pdf", []string{"/test/doc.pdf", "doc.pdf", fmt.Sprintf("%s%s/test/doc.pdf", s3fs.Prefix, bucketName)}},
		{"/", []string{"/", "", fmt.Sprintf("%s%s/", s3fs.Prefix, bucketName)}},
	}

	for _, tc := range testCases {
		f := s3.File(tc.filePath)
		assert.NotNil(t, f)
		assert.NotEmpty(t, f)
		assert.Equal(t, tc.vals[0], f.Path())
		assert.Equal(t, tc.vals[1], f.Name())
		assert.Equal(t, tc.vals[2], f.URL())
	}
}

func TestJoinCleanFile(t *testing.T) {
	testCases := []struct {
		input string
		want  fs.File
	}{
		{fmt.Sprintf("s3://%s/test.pdf", bucketName), fs.File(fmt.Sprintf("s3://%s/test.pdf", bucketName))},
		{"test.pdf", fs.File(fmt.Sprintf("s3://%s/test.pdf", bucketName))},
		{"/test.pdf", fs.File(fmt.Sprintf("s3://%s/test.pdf", bucketName))},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.JoinCleanFile(tc.input))
	}
}

func TestURL(t *testing.T) {
	assert.Equal(t, fmt.Sprintf("s3://%s/test/test.pdf", bucketName), s3.URL("/test/test.pdf"))
}

func TestJoinCleanPath(t *testing.T) {
	testCases := []defaultPathTestCase{
		{fmt.Sprintf("s3://%s/test.pdf", bucketName), "/test.pdf"},
		{"test.pdf", "/test.pdf"},
		{"/test.pdf", "/test.pdf"},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.JoinCleanPath(tc.input))
	}
}

func TestSplitPath(t *testing.T) {
	testCases := []struct {
		input string
		want  []string
	}{
		{fmt.Sprintf("s3://%s/test/test.pdf", bucketName), []string{"test", "test.pdf"}},
		{"/test/test.pdf", []string{"test", "test.pdf"}},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.SplitPath(tc.input))
	}
}

func TestDirAndName(t *testing.T) {
	testCases := []struct {
		input string
		want  [2]string
	}{
		{fmt.Sprintf("s3://%s/test/test.pdf", bucketName), [2]string{fmt.Sprintf("s3://%s/test", bucketName), "test.pdf"}},
		{"/test/test.pdf", [2]string{"/test", "test.pdf"}},
	}
	for _, tc := range testCases {
		dir, name := s3.DirAndName(tc.input)
		res := [2]string{dir, name}
		assert.Equal(t, tc.want, res)
	}
}

func TestIsAbsPath(t *testing.T) {
	assert.True(t, s3.IsAbsPath("/test/test.pdf"))
	assert.False(t, s3.IsAbsPath("test.df"))
}

func TestAbsPath(t *testing.T) {
	testCases := []defaultPathTestCase{
		{fmt.Sprintf("s3://%s/test/test.pdf", bucketName), fmt.Sprintf("s3://%s/test/test.pdf", bucketName)},
		{"test.pdf", "/test.pdf"},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.want, s3.AbsPath(tc.input))
	}
}

func TestMakeDir(t *testing.T) {
	for _, d := range directories {
		assert.NoError(t, s3.MakeDir(d, nil))
		assert.True(t, s3.Stat(d+"/").Exists)
	}
}

func TestWriteAll(t *testing.T) {
	for _, f := range files {
		assert.NoError(t, s3.WriteAll(f, testData, nil))
		text, err := s3.File(f).ReadAllString()
		assert.NoError(t, err)
		assert.Equal(t, string(testData), text)
	}
}

func TestStat(t *testing.T) {
	for _, f := range files {
		assert.NotEmpty(t, s3.Stat(f))
	}
}

func TestStatNotExists(t *testing.T) {
	i := s3.Stat("DOESNOTEXIST/")
	assert.Equal(t, false, i.Exists)
}

func TestIsHidden(t *testing.T) {
	testCases := []struct {
		path   string
		hidden bool
	}{
		{".test.txt", true},
		{"test.txt", false},
		{fmt.Sprintf("%s%s/.test.txt", s3fs.Prefix, bucketName), true},
		{fmt.Sprintf("%s%s/test.txt", s3fs.Prefix, bucketName), false},
		{"/.test", true},
	}
	for _, tc := range testCases {
		assert.Equal(t, tc.hidden, s3.IsHidden(tc.path))
	}
}

func TestListDirInfo(t *testing.T) {
	expectedOutput := []string{"test.txt", "test", "test1"}

	result := []string{}
	assert.NoError(t, s3.ListDirInfo("/", func(file fs.File, _ fs.FileInfo) error {
		result = append(result, file.Path())
		return nil
	}, nil))

	assert.EqualValues(t, expectedOutput, result)
}

func TestListDirInfoRecursive(t *testing.T) {
	expectedOutput := append(directories, files...)
	assert.NoError(t, s3.ListDirInfoRecursive("/", func(file fs.File, _ fs.FileInfo) error {
		assert.Contains(t, expectedOutput, file.Path())
		return nil
	}, nil))
}

func TestListDirMax(t *testing.T) {
	files, err := s3.ListDirMax("/", 2, nil)
	assert.NoError(t, err)
	assert.Condition(t, assert.Comparison(func() bool {
		return len(files) <= 2
	}), "Too many object keys returned")
}

func TestTouch(t *testing.T) {
	path := "test1/touchtest"
	files = append(files, path)
	assert.NoError(t, s3.Touch(path, nil))
	assert.True(t, s3.File(path).Exists())
}

func TestTruncate(t *testing.T) {
	assert.NoError(t, s3.Truncate(files[0], 2))
	data, err := s3.ReadAll(files[0])
	assert.NoError(t, err)
	assert.Len(t, data, 2)
}

func TestCopyFile(t *testing.T) {
	parts := strings.Split(files[1], ".")
	targetPath := parts[0] + "_copy." + parts[1]
	assert.NoError(t, s3.CopyFile(files[1], targetPath, nil))
	assert.True(t, s3.Stat(targetPath).Exists)
	dataInCopy, err := s3.ReadAll(targetPath)
	assert.NoError(t, err)
	assert.Equal(t, testData, dataInCopy)
	files = append(files, targetPath)
}

func TestRename(t *testing.T) {
	originalData, err := s3.ReadAll(files[1])
	assert.NoError(t, err)

	parts := strings.Split(files[1], ".")
	renamedFile := path.Base(parts[0] + "_renamed." + parts[1])
	assert.NoError(t, s3.Rename(files[1], renamedFile))

	// Renamed file has to exist
	renamedPath := path.Join(path.Dir(files[1]), renamedFile)
	assert.True(t, s3.Stat(renamedPath).Exists)

	// Content has to be equal
	dataOfRenamedFile, err := s3.ReadAll(renamedPath)
	assert.NoError(t, err)
	assert.Equal(t, originalData, dataOfRenamedFile)

	// Original file cannot exist
	assert.False(t, s3.Stat(files[1]).Exists)

	files = append(files, renamedPath)
}

func TestMove(t *testing.T) {
	originalData, err := s3.ReadAll(files[2])
	source := files[2]
	dest := path.Base(files[2]) // Simply move to root directory
	assert.NoError(t, err)
	assert.NoError(t, s3.Move(source, dest))
	assert.False(t, s3.Stat(source).Exists)
	assert.True(t, s3.Stat(dest).Exists)

	dataAfterMove, err := s3.ReadAll(dest)
	assert.NoError(t, err)
	assert.Equal(t, originalData, dataAfterMove)

	files = append(files, dest)
}

// Also test if using fs.File's RemoveRecursive method works with S3.
func TestRemoveRecursive(t *testing.T) {
	assert.NoError(t, s3.File(directories[0]).RemoveRecursive())
	// This directory actually had contents before so if it doesn't exist
	// all of its contents were also deleted.
	assert.False(t, s3.Stat(directories[0]).Exists)
}

func TestRemove(t *testing.T) {
	for _, d := range directories {
		// The directory keys do not have a trailing slash, but trailing slashes
		// are required for S3 to delete directories. See the description at the
		// very top.
		assert.NoError(t, s3.Remove(d+s3fs.Separator))
	}
	for _, f := range files {
		assert.NoError(t, s3.Remove(f))
	}
}
<|endoftext|>


<commit_before>package rel

type WindowNode struct {
	Orders  *[]Visitable
	Framing Visitable
	BaseVisitable
}

func (node *WindowNode) Order(v Visitable) *WindowNode {
	if node.Orders == nil {
		node.Orders = &[]Visitable{}
	}
	*node.Orders = append(*node.Orders, v)
	return node
}

type NamedWindowNode struct {
	Name    SqlLiteralNode
	Orders  *[]Visitable
	Framing Visitable
	BaseVisitable
}

func (node *NamedWindowNode) Order(v Visitable) {
	if node.Orders == nil {
		node.Orders = &[]Visitable{}
	}
	*node.Orders = append(*node.Orders, v)
}

func (node *NamedWindowNode) Rows(v Visitable) Visitable {
	return node.Frame(&RowsNode{Expr: v})
}

func (node *NamedWindowNode) Frame(v Visitable) Visitable {
	node.Framing = v
	return node.Framing
}

func (node *NamedWindowNode) Range(v Visitable) Visitable {
	return node.Frame(&RangeNode{Expr: v})
}
<commit_msg>NamedWindowNode#Order should return itself<commit_after>package rel

type WindowNode struct {
	Orders  *[]Visitable
	Framing Visitable
	BaseVisitable
}

func (node *WindowNode) Order(v Visitable) *WindowNode {
	if node.Orders == nil {
		node.Orders = &[]Visitable{}
	}
	*node.Orders = append(*node.Orders, v)
	return node
}

type NamedWindowNode struct {
	Name    SqlLiteralNode
	Orders  *[]Visitable
	Framing Visitable
	BaseVisitable
}

func (node *NamedWindowNode) Order(v Visitable) *NamedWindowNode {
	if node.Orders == nil {
		node.Orders = &[]Visitable{}
	}
	*node.Orders = append(*node.Orders, v)
	return node
}

func (node *NamedWindowNode) Rows(v Visitable) Visitable {
	return node.Frame(&RowsNode{Expr: v})
}

func (node *NamedWindowNode) Frame(v Visitable) Visitable {
	node.Framing = v
	return node.Framing
}

func (node *NamedWindowNode) Range(v Visitable) Visitable {
	return node.Frame(&RangeNode{Expr: v})
}
<|endoftext|>


<commit_before>/*
 Copyright 2018 Padduck, 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 api

import (
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"github.com/pufferpanel/pufferpanel/v2"
	"github.com/pufferpanel/pufferpanel/v2/middleware"
	"github.com/pufferpanel/pufferpanel/v2/middleware/handlers"
	"github.com/pufferpanel/pufferpanel/v2/models"
	"github.com/pufferpanel/pufferpanel/v2/response"
	"github.com/pufferpanel/pufferpanel/v2/services"
	"github.com/spf13/cast"
	"net/http"
)

func registerUsers(g *gin.RouterGroup) {
	g.Handle("GET", "", handlers.OAuth2Handler(pufferpanel.ScopeUsersView, false), searchUsers)
	g.Handle("POST", "", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), createUser)
	g.Handle("OPTIONS", "", response.CreateOptions("GET", "POST"))

	g.Handle("GET", "/:id", handlers.OAuth2Handler(pufferpanel.ScopeUsersView, false), getUser)
	g.Handle("POST", "/:id", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), updateUser)
	g.Handle("DELETE", "/:id", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), deleteUser)
	g.Handle("OPTIONS", "/:id", response.CreateOptions("GET", "POST", "DELETE"))

	g.Handle("GET", "/:id/perms", handlers.OAuth2Handler(pufferpanel.ScopeUsersView, false), getUserPerms)
	g.Handle("PUT", "/:id/perms", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), setUserPerms)
	g.Handle("OPTIONS", "/:id/perms", response.CreateOptions("PUT", "GET"))
}

func searchUsers(c *gin.Context) {
	var err error
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	search := newUserSearch()
	err = c.ShouldBind(search)
	if response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if search.PageLimit > MaxPageSize {
		search.PageLimit = MaxPageSize
	}

	var results *models.Users
	var total uint
	if results, total, err = us.Search(search.Username, search.Email, search.PageLimit, search.Page); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.JSON(http.StatusOK, &models.UserSearchResponse{
		Users: models.FromUsers(results),
		Metadata: &response.Metadata{Paging: &response.Paging{
			Page:    search.Page,
			Size:    search.PageLimit,
			MaxSize: MaxPageSize,
			Total:   total,
		}},
	})
}

func createUser(c *gin.Context) {
	var err error
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var viewModel models.UserView
	if err = c.BindJSON(&viewModel); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if err = viewModel.Valid(false); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if viewModel.Password == "" {
		response.HandleError(c, pufferpanel.ErrFieldRequired("password"), http.StatusBadRequest)
		return
	}

	user := &models.User{}
	viewModel.CopyToModel(user)

	if err = us.Create(user); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	resultModel := models.FromUser(user)

	c.JSON(http.StatusOK, resultModel)
}

func getUser(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.JSON(http.StatusOK, models.FromUser(user))
}

func updateUser(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	var viewModel models.UserView
	if err := c.BindJSON(&viewModel); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if err := viewModel.Valid(true); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	viewModel.CopyToModel(user)

	if err = us.Update(user); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.Status(http.StatusNoContent)
}

func deleteUser(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	if err = us.Delete(user); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.Status(http.StatusNoContent)
}

func getUserPerms(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}
	ps := &services.Permission{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	perms, err := ps.GetForUserAndServer(user.ID, nil)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.JSON(http.StatusOK, models.FromPermission(perms))
}

func setUserPerms(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}
	ps := &services.Permission{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	viewModel := &models.PermissionView{}
	err = c.BindJSON(viewModel)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	perms, err := ps.GetForUserAndServer(user.ID, nil)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	viewModel.CopyTo(perms, true)

	err = ps.UpdatePermissions(perms)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.Status(http.StatusNoContent)
}

func newUserSearch() *models.UserSearch {
	return &models.UserSearch{
		Username:  "*",
		Email:     "*",
		PageLimit: DefaultPageSize,
		Page:      1,
	}
}
<commit_msg>Finish panel api documentation<commit_after>/*
 Copyright 2018 Padduck, 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 api

import (
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"github.com/pufferpanel/pufferpanel/v2"
	"github.com/pufferpanel/pufferpanel/v2/middleware"
	"github.com/pufferpanel/pufferpanel/v2/middleware/handlers"
	"github.com/pufferpanel/pufferpanel/v2/models"
	"github.com/pufferpanel/pufferpanel/v2/response"
	"github.com/pufferpanel/pufferpanel/v2/services"
	"github.com/spf13/cast"
	"net/http"
)

func registerUsers(g *gin.RouterGroup) {
	g.Handle("GET", "", handlers.OAuth2Handler(pufferpanel.ScopeUsersView, false), searchUsers)
	g.Handle("POST", "", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), createUser)
	g.Handle("OPTIONS", "", response.CreateOptions("GET", "POST"))

	g.Handle("GET", "/:id", handlers.OAuth2Handler(pufferpanel.ScopeUsersView, false), getUser)
	g.Handle("POST", "/:id", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), updateUser)
	g.Handle("DELETE", "/:id", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), deleteUser)
	g.Handle("OPTIONS", "/:id", response.CreateOptions("GET", "POST", "DELETE"))

	g.Handle("GET", "/:id/perms", handlers.OAuth2Handler(pufferpanel.ScopeUsersView, false), getUserPerms)
	g.Handle("PUT", "/:id/perms", handlers.OAuth2Handler(pufferpanel.ScopeUsersEdit, false), setUserPerms)
	g.Handle("OPTIONS", "/:id/perms", response.CreateOptions("PUT", "GET"))
}

// @Summary Get users
// @Description Gets users, and allowing for filtering of users. * is a wildcard that can be used for text inputs
// @Accept json
// @Produce json
// @Success 200 {object} models.UserSearchResponse
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param body body models.UserSearch true "Filters to search on"
// @Router /api/users [get]
func searchUsers(c *gin.Context) {
	var err error
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	search := newUserSearch()
	err = c.ShouldBind(search)
	if response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if search.PageLimit > MaxPageSize {
		search.PageLimit = MaxPageSize
	}

	var results *models.Users
	var total uint
	if results, total, err = us.Search(search.Username, search.Email, search.PageLimit, search.Page); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.JSON(http.StatusOK, &models.UserSearchResponse{
		Users: models.FromUsers(results),
		Metadata: &response.Metadata{Paging: &response.Paging{
			Page:    search.Page,
			Size:    search.PageLimit,
			MaxSize: MaxPageSize,
			Total:   total,
		}},
	})
}

// @Summary Create user
// @Accept json
// @Produce json
// @Success 200 {object} models.UserView
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param body body models.UserView true "New user information"
// @Router /api/users [post]
func createUser(c *gin.Context) {
	var err error
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var viewModel models.UserView
	if err = c.BindJSON(&viewModel); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if err = viewModel.Valid(false); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if viewModel.Password == "" {
		response.HandleError(c, pufferpanel.ErrFieldRequired("password"), http.StatusBadRequest)
		return
	}

	user := &models.User{}
	viewModel.CopyToModel(user)

	if err = us.Create(user); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	resultModel := models.FromUser(user)

	c.JSON(http.StatusOK, resultModel)
}

// @Summary Get a user
// @Accept json
// @Produce json
// @Success 200 {object} models.UserView
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param id path int true "User ID"
// @Router /api/users/{id} [get]
func getUser(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.JSON(http.StatusOK, models.FromUser(user))
}

// @Summary Update user
// @Accept json
// @Produce json
// @Success 204 {object} response.Empty
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param id path uint true "User ID"
// @Param body body models.UserView true "New user information"
// @Router /api/users/{id} [post]
func updateUser(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	var viewModel models.UserView
	if err := c.BindJSON(&viewModel); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	if err := viewModel.Valid(true); response.HandleError(c, err, http.StatusBadRequest) {
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	viewModel.CopyToModel(user)

	if err = us.Update(user); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.Status(http.StatusNoContent)
}

// @Summary Delete user
// @Accept json
// @Produce json
// @Success 204 {object} response.Empty
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param id path uint true "User ID"
// @Router /api/users/{id} [delete]
func deleteUser(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	if err = us.Delete(user); response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.Status(http.StatusNoContent)
}

// @Summary Gets user permissions
// @Accept json
// @Produce json
// @Success 200 {object} models.PermissionView
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param id path uint true "User ID"
// @Router /api/users/{id}/perms [get]
func getUserPerms(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}
	ps := &services.Permission{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	perms, err := ps.GetForUserAndServer(user.ID, nil)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.JSON(http.StatusOK, models.FromPermission(perms))
}

// @Summary Sets user permissions
// @Accept json
// @Produce json
// @Success 204 {object} response.Empty
// @Failure 400 {object} response.Error
// @Failure 403 {object} response.Error
// @Failure 404 {object} response.Error
// @Failure 500 {object} response.Error
// @Param id path uint true "User ID"
// @Param body body models.PermissionView true "New permissions"
// @Router /api/users/{id}/perms [put]
func setUserPerms(c *gin.Context) {
	db := middleware.GetDatabase(c)
	us := &services.User{DB: db}
	ps := &services.Permission{DB: db}

	var err error
	var id uint
	if id, err = cast.ToUintE(c.Param("id")); err != nil {
		response.HandleError(c, err, http.StatusBadRequest)
		return
	}

	viewModel := &models.PermissionView{}
	err = c.BindJSON(viewModel)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	user, err := us.GetById(id)
	if err != nil && gorm.IsRecordNotFoundError(err) {
		c.AbortWithStatus(http.StatusNotFound)
		return
	} else if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	perms, err := ps.GetForUserAndServer(user.ID, nil)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	viewModel.CopyTo(perms, true)

	err = ps.UpdatePermissions(perms)
	if response.HandleError(c, err, http.StatusInternalServerError) {
		return
	}

	c.Status(http.StatusNoContent)
}

func newUserSearch() *models.UserSearch {
	return &models.UserSearch{
		Username:  "*",
		Email:     "*",
		PageLimit: DefaultPageSize,
		Page:      1,
	}
}
<|endoftext|>


<commit_before>package web

import (
	"net/http"

	"github.com/remeh/reddit-audiences/app"

	"github.com/gorilla/mux"
)

type Audiences struct {
	App *app.App
}

type audiencesBody struct {
	Subreddit string
}

func (c Audiences) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	t := c.App.Templates.Lookup("audiences.html")

	vars := mux.Vars(r)
	subreddit := vars["subreddit"]

	// test if known subreddit

	if len(subreddit) == 0 {
		http.Redirect(w, r, "/", 301)
		return
	}

	t = t.Funcs(app.TemplateHelpers())

	t.Execute(w, audiencesBody{
		Subreddit: subreddit,
	})
}
<commit_msg>web: lenient audience route.<commit_after>package web

import (
	"net/http"
	"strings"

	"github.com/remeh/reddit-audiences/app"

	"github.com/gorilla/mux"
)

type Audiences struct {
	App *app.App
}

type audiencesBody struct {
	Subreddit string
}

func (c Audiences) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	t := c.App.Templates.Lookup("audiences.html")

	vars := mux.Vars(r)
	subreddit := vars["subreddit"]

	// test if known subreddit
	subreddit = strings.ToLower(strings.Trim(subreddit, " "))
	if len(subreddit) == 0 {
		http.Redirect(w, r, "/", 301)
		return
	}

	t = t.Funcs(app.TemplateHelpers())

	t.Execute(w, audiencesBody{
		Subreddit: subreddit,
	})
}
<|endoftext|>


<commit_before>package subscribe

import (
	"fmt"

	"github.com/silenceper/wechat/v2/miniprogram/context"
	"github.com/silenceper/wechat/v2/util"
)

const (
	// 发送订阅消息
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
	subscribeSendURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"

	// 获取当前帐号下的个人模板列表
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
	getTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate"

	// 添加订阅模板
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html
	addTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate"

	// 删除私有模板
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.deleteTemplate.html
	delTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate"

	// 统一服务消息
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html
	uniformMessageSend = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send"
)

// Subscribe 订阅消息
type Subscribe struct {
	*context.Context
}

// NewSubscribe 实例化
func NewSubscribe(ctx *context.Context) *Subscribe {
	return &Subscribe{Context: ctx}
}

// Message 订阅消息请求参数
type Message struct {
	ToUser           string               `json:"touser"`            // 必选，接收者（用户）的 openid
	TemplateID       string               `json:"template_id"`       // 必选，所需下发的订阅模板id
	Page             string               `json:"page"`              // 可选，点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转。
	Data             map[string]*DataItem `json:"data"`              // 必选, 模板内容
	MiniprogramState string               `json:"miniprogram_state"` // 可选，跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
	Lang             string               `json:"lang"`              // 入小程序查看”的语言类型，支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文)，默认为zh_CN
}

// DataItem 模版内某个 .DATA 的值
type DataItem struct {
	Value interface{} `json:"value"`
	Color string      `json:"color"`
}

// TemplateItem template item
type TemplateItem struct {
	PriTmplID string `json:"priTmplId"`
	Title     string `json:"title"`
	Content   string `json:"content"`
	Example   string `json:"example"`
	Type      int64  `json:"type"`
}

// TemplateList template list
type TemplateList struct {
	util.CommonError
	Data []TemplateItem `json:"data"`
}

// Send 发送订阅消息
func (s *Subscribe) Send(msg *Message) (err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", subscribeSendURL, accessToken)
	response, err := util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	return util.DecodeWithCommonError(response, "Send")
}

// ListTemplates 获取当前帐号下的个人模板列表
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
func (s *Subscribe) ListTemplates() (*TemplateList, error) {
	accessToken, err := s.GetAccessToken()
	if err != nil {
		return nil, err
	}
	uri := fmt.Sprintf("%s?access_token=%s", getTemplateURL, accessToken)
	response, err := util.HTTPGet(uri)
	if err != nil {
		return nil, err
	}
	templateList := TemplateList{}
	err = util.DecodeWithError(response, &templateList, "ListTemplates")
	if err != nil {
		return nil, err
	}
	return &templateList, nil
}

// UniformMessage 统一服务消息
type UniformMessage struct {
	ToUser           string `json:"touser"`
	WeappTemplateMsg struct {
		TemplateID      string               `json:"template_id"`
		Page            string               `json:"page"`
		FormID          string               `json:"form_id"`
		Data            map[string]*DataItem `json:"data"`
		EmphasisKeyword string               `json:"emphasis_keyword"`
	} `json:"weapp_template_msg"`
	MpTemplateMsg struct {
		Appid       string `json:"appid"`
		TemplateID  string `json:"template_id"`
		URL         string `json:"url"`
		Miniprogram struct {
			Appid    string `json:"appid"`
			Pagepath string `json:"pagepath"`
		} `json:"miniprogram"`
		Data map[string]*DataItem `json:"data"`
	} `json:"mp_template_msg"`
}

// UniformSend 发送统一服务消息
func (s *Subscribe) UniformSend(msg *UniformMessage) (err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", uniformMessageSend, accessToken)
	response, err := util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	return util.DecodeWithCommonError(response, "UniformSend")
}

type resSubscribeAdd struct {
	util.CommonError

	TemplateID string `json:"priTmplId"`
}

// Add 添加订阅消息模板
func (s *Subscribe) Add(ShortID string, kidList []int, sceneDesc string) (templateID string, err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	var msg = struct {
		TemplateIDShort string `json:"tid"`
		SceneDesc       string `json:"sceneDesc"`
		KidList         []int  `json:"kidList"`
	}{TemplateIDShort: ShortID, SceneDesc: sceneDesc, KidList: kidList}
	uri := fmt.Sprintf("%s?access_token=%s", addTemplateURL, accessToken)
	var response []byte
	response, err = util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	var result resSubscribeAdd
	err = util.DecodeWithError(response, &result, "AddSubscribe")
	if err != nil {
		return
	}
	templateID = result.TemplateID
	return
}

// Delete 删除私有模板
func (s *Subscribe) Delete(templateID string) (err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	var msg = struct {
		TemplateID string `json:"priTmplId"`
	}{TemplateID: templateID}
	uri := fmt.Sprintf("%s?access_token=%s", delTemplateURL, accessToken)
	var response []byte
	response, err = util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	return util.DecodeWithCommonError(response, "DeleteSubscribe")
}
<commit_msg>fix: 统一服务信息，公众号消息模板字段bug (#547)<commit_after>package subscribe

import (
	"fmt"

	"github.com/silenceper/wechat/v2/miniprogram/context"
	"github.com/silenceper/wechat/v2/util"
)

const (
	// 发送订阅消息
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
	subscribeSendURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"

	// 获取当前帐号下的个人模板列表
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
	getTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate"

	// 添加订阅模板
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html
	addTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate"

	// 删除私有模板
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.deleteTemplate.html
	delTemplateURL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate"

	// 统一服务消息
	// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html
	uniformMessageSend = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send"
)

// Subscribe 订阅消息
type Subscribe struct {
	*context.Context
}

// NewSubscribe 实例化
func NewSubscribe(ctx *context.Context) *Subscribe {
	return &Subscribe{Context: ctx}
}

// Message 订阅消息请求参数
type Message struct {
	ToUser           string               `json:"touser"`            // 必选，接收者（用户）的 openid
	TemplateID       string               `json:"template_id"`       // 必选，所需下发的订阅模板id
	Page             string               `json:"page"`              // 可选，点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转。
	Data             map[string]*DataItem `json:"data"`              // 必选, 模板内容
	MiniprogramState string               `json:"miniprogram_state"` // 可选，跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
	Lang             string               `json:"lang"`              // 入小程序查看”的语言类型，支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文)，默认为zh_CN
}

// DataItem 模版内某个 .DATA 的值
type DataItem struct {
	Value interface{} `json:"value"`
	Color string      `json:"color"`
}

// TemplateItem template item
type TemplateItem struct {
	PriTmplID string `json:"priTmplId"`
	Title     string `json:"title"`
	Content   string `json:"content"`
	Example   string `json:"example"`
	Type      int64  `json:"type"`
}

// TemplateList template list
type TemplateList struct {
	util.CommonError
	Data []TemplateItem `json:"data"`
}

// Send 发送订阅消息
func (s *Subscribe) Send(msg *Message) (err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", subscribeSendURL, accessToken)
	response, err := util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	return util.DecodeWithCommonError(response, "Send")
}

// ListTemplates 获取当前帐号下的个人模板列表
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html
func (s *Subscribe) ListTemplates() (*TemplateList, error) {
	accessToken, err := s.GetAccessToken()
	if err != nil {
		return nil, err
	}
	uri := fmt.Sprintf("%s?access_token=%s", getTemplateURL, accessToken)
	response, err := util.HTTPGet(uri)
	if err != nil {
		return nil, err
	}
	templateList := TemplateList{}
	err = util.DecodeWithError(response, &templateList, "ListTemplates")
	if err != nil {
		return nil, err
	}
	return &templateList, nil
}

// UniformMessage 统一服务消息
type UniformMessage struct {
	ToUser           string `json:"touser"`
	WeappTemplateMsg struct {
		TemplateID      string               `json:"template_id"`
		Page            string               `json:"page"`
		FormID          string               `json:"form_id"`
		Data            map[string]*DataItem `json:"data"`
		EmphasisKeyword string               `json:"emphasis_keyword"`
	} `json:"weapp_template_msg"`
	MpTemplateMsg struct {
		Appid       string `json:"appid"`
		TemplateID  string `json:"template_id"`
		URL         string `json:"url"`
		Miniprogram struct {
			Appid    string `json:"appid"`
			Pagepath string `json:"page"`
		} `json:"miniprogram"`
		Data map[string]*DataItem `json:"data"`
	} `json:"mp_template_msg"`
}

// UniformSend 发送统一服务消息
func (s *Subscribe) UniformSend(msg *UniformMessage) (err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	uri := fmt.Sprintf("%s?access_token=%s", uniformMessageSend, accessToken)
	response, err := util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	return util.DecodeWithCommonError(response, "UniformSend")
}

type resSubscribeAdd struct {
	util.CommonError

	TemplateID string `json:"priTmplId"`
}

// Add 添加订阅消息模板
func (s *Subscribe) Add(ShortID string, kidList []int, sceneDesc string) (templateID string, err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	var msg = struct {
		TemplateIDShort string `json:"tid"`
		SceneDesc       string `json:"sceneDesc"`
		KidList         []int  `json:"kidList"`
	}{TemplateIDShort: ShortID, SceneDesc: sceneDesc, KidList: kidList}
	uri := fmt.Sprintf("%s?access_token=%s", addTemplateURL, accessToken)
	var response []byte
	response, err = util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	var result resSubscribeAdd
	err = util.DecodeWithError(response, &result, "AddSubscribe")
	if err != nil {
		return
	}
	templateID = result.TemplateID
	return
}

// Delete 删除私有模板
func (s *Subscribe) Delete(templateID string) (err error) {
	var accessToken string
	accessToken, err = s.GetAccessToken()
	if err != nil {
		return
	}
	var msg = struct {
		TemplateID string `json:"priTmplId"`
	}{TemplateID: templateID}
	uri := fmt.Sprintf("%s?access_token=%s", delTemplateURL, accessToken)
	var response []byte
	response, err = util.PostJSON(uri, msg)
	if err != nil {
		return
	}
	return util.DecodeWithCommonError(response, "DeleteSubscribe")
}
<|endoftext|>


<commit_before>package world

import (
	"fmt"
	"sync"
	"time"

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

const (
	worldEventsChanMainBufferSize  = 4096
	worldEventsChanProxyBufferSize = 4096
)

const worldEventsSendTimeout = time.Millisecond

type World struct {
	pg          *playground.Playground
	chMain      chan Event
	chsProxy    []chan Event
	chsProxyMux *sync.RWMutex
	stopGlobal  chan struct{}
	flagStarted bool
	startedMux  *sync.Mutex
}

func NewWorld(width, height uint8) (*World, error) {
	pg, err := playground.NewPlayground(width, height)
	if err != nil {
		return nil, fmt.Errorf("cannot create world: %s", err)
	}

	return &World{
		pg:          pg,
		chMain:      make(chan Event, worldEventsChanMainBufferSize),
		chsProxy:    make([]chan Event, 0),
		chsProxyMux: &sync.RWMutex{},
		stopGlobal:  make(chan struct{}),

		flagStarted: false,
		startedMux:  &sync.Mutex{},
	}, nil
}

func (w *World) event(event Event) {
	select {
	case w.chMain <- event:
	case <-w.stopGlobal:
	}
}

func (w *World) Start(stop <-chan struct{}) {
	w.startedMux.Lock()
	defer w.startedMux.Unlock()

	if w.flagStarted {
		return
	}
	w.flagStarted = true

	go func() {
		select {
		case <-stop:
		}
		w.stop()
	}()

	go func() {
		for {
			select {
			case event, ok := <-w.chMain:
				if !ok {
					return
				}
				w.broadcast(event)
			case <-w.stopGlobal:
				return
			}
		}
	}()
}

func (w *World) broadcast(event Event) {
	w.chsProxyMux.RLock()
	defer w.chsProxyMux.RUnlock()

	for _, chProxy := range w.chsProxy {
		select {
		case chProxy <- event:
		case <-w.stopGlobal:
		}
	}
}

func (w *World) createChanProxy() chan Event {
	chProxy := make(chan Event, worldEventsChanProxyBufferSize)

	w.chsProxyMux.Lock()
	w.chsProxy = append(w.chsProxy, chProxy)
	w.chsProxyMux.Unlock()

	return chProxy
}

func (w *World) deleteChanProxy(chProxy chan Event) {
	go func() {
		for range chProxy {
		}
	}()

	w.chsProxyMux.Lock()
	for i := range w.chsProxy {
		if w.chsProxy[i] == chProxy {
			w.chsProxy = append(w.chsProxy[:i], w.chsProxy[i+1:]...)
			close(chProxy)
			break
		}
	}
	w.chsProxyMux.Unlock()
}

func (w *World) Events(stop <-chan struct{}, buffer uint) <-chan Event {
	chProxy := w.createChanProxy()
	chOut := make(chan Event, buffer)

	go func() {
		defer close(chOut)
		defer w.deleteChanProxy(chProxy)

		for {
			select {
			case <-stop:
				return
			case <-w.stopGlobal:
				return
			case event := <-chProxy:
				w.sendEvent(chOut, event, stop)
			}
		}
	}()

	return chOut
}

func (w *World) sendEvent(ch chan Event, event Event, stop <-chan struct{}) {
	if event.Type == EventTypeObjectUpdate || event.Type == EventTypeObjectChecked {
		w.sendEventTimeout(ch, event, stop, worldEventsSendTimeout)
	} else {
		w.sendEventStrict(ch, event, stop)
	}
}

func (w *World) sendEventStrict(ch chan Event, event Event, stop <-chan struct{}) {
	select {
	case ch <- event:
	case <-w.stopGlobal:
	case <-stop:
	}
}

func (w *World) sendEventTimeout(ch chan Event, event Event, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()
	select {
	case ch <- event:
	case <-w.stopGlobal:
	case <-stop:
	case <-timer.C:
	}
}

func (w *World) stop() {
	close(w.stopGlobal)
	close(w.chMain)

	w.chsProxyMux.Lock()
	defer w.chsProxyMux.Unlock()

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

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

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) 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, error) {
	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, nil
}

func (w *World) DeleteObject(object interface{}, location engine.Location) error {
	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 nil
}

func (w *World) UpdateObject(object interface{}, old, new engine.Location) error {
	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, error) {
	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, nil
}

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, nil
}

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, nil
}

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

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

func (w *World) LocationOccupied(location engine.Location) bool {
	return w.pg.LocationOccupied(location)
}

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()
}

func (w *World) GetObjects() []interface{} {
	return w.pg.GetObjects()
}
<commit_msg>issue #23 Create in World methods ObtainIdentifier and ReleaseIdentifier<commit_after>package world

import (
	"fmt"
	"sync"
	"time"

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

const (
	worldEventsChanMainBufferSize  = 4096
	worldEventsChanProxyBufferSize = 4096
)

const worldEventsSendTimeout = time.Millisecond

type World struct {
	pg          *playground.Playground
	chMain      chan Event
	chsProxy    []chan Event
	chsProxyMux *sync.RWMutex
	stopGlobal  chan struct{}
	flagStarted bool
	startedMux  *sync.Mutex

	identifierRegistry *IdentifierRegistry
}

func NewWorld(width, height uint8) (*World, error) {
	pg, err := playground.NewPlayground(width, height)
	if err != nil {
		return nil, fmt.Errorf("cannot create world: %s", err)
	}

	return &World{
		pg:          pg,
		chMain:      make(chan Event, worldEventsChanMainBufferSize),
		chsProxy:    make([]chan Event, 0),
		chsProxyMux: &sync.RWMutex{},
		stopGlobal:  make(chan struct{}),

		flagStarted: false,
		startedMux:  &sync.Mutex{},

		identifierRegistry: NewIdentifierRegistry(),
	}, nil
}

func (w *World) event(event Event) {
	select {
	case w.chMain <- event:
	case <-w.stopGlobal:
	}
}

func (w *World) Start(stop <-chan struct{}) {
	w.startedMux.Lock()
	defer w.startedMux.Unlock()

	if w.flagStarted {
		return
	}
	w.flagStarted = true

	go func() {
		select {
		case <-stop:
		}
		w.stop()
	}()

	go func() {
		for {
			select {
			case event, ok := <-w.chMain:
				if !ok {
					return
				}
				w.broadcast(event)
			case <-w.stopGlobal:
				return
			}
		}
	}()
}

func (w *World) broadcast(event Event) {
	w.chsProxyMux.RLock()
	defer w.chsProxyMux.RUnlock()

	for _, chProxy := range w.chsProxy {
		select {
		case chProxy <- event:
		case <-w.stopGlobal:
		}
	}
}

func (w *World) createChanProxy() chan Event {
	chProxy := make(chan Event, worldEventsChanProxyBufferSize)

	w.chsProxyMux.Lock()
	w.chsProxy = append(w.chsProxy, chProxy)
	w.chsProxyMux.Unlock()

	return chProxy
}

func (w *World) deleteChanProxy(chProxy chan Event) {
	go func() {
		for range chProxy {
		}
	}()

	w.chsProxyMux.Lock()
	for i := range w.chsProxy {
		if w.chsProxy[i] == chProxy {
			w.chsProxy = append(w.chsProxy[:i], w.chsProxy[i+1:]...)
			close(chProxy)
			break
		}
	}
	w.chsProxyMux.Unlock()
}

func (w *World) Events(stop <-chan struct{}, buffer uint) <-chan Event {
	chProxy := w.createChanProxy()
	chOut := make(chan Event, buffer)

	go func() {
		defer close(chOut)
		defer w.deleteChanProxy(chProxy)

		for {
			select {
			case <-stop:
				return
			case <-w.stopGlobal:
				return
			case event := <-chProxy:
				w.sendEvent(chOut, event, stop)
			}
		}
	}()

	return chOut
}

func (w *World) sendEvent(ch chan Event, event Event, stop <-chan struct{}) {
	if event.Type == EventTypeObjectUpdate || event.Type == EventTypeObjectChecked {
		w.sendEventTimeout(ch, event, stop, worldEventsSendTimeout)
	} else {
		w.sendEventStrict(ch, event, stop)
	}
}

func (w *World) sendEventStrict(ch chan Event, event Event, stop <-chan struct{}) {
	select {
	case ch <- event:
	case <-w.stopGlobal:
	case <-stop:
	}
}

func (w *World) sendEventTimeout(ch chan Event, event Event, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()
	select {
	case ch <- event:
	case <-w.stopGlobal:
	case <-stop:
	case <-timer.C:
	}
}

func (w *World) stop() {
	close(w.stopGlobal)
	close(w.chMain)

	w.chsProxyMux.Lock()
	defer w.chsProxyMux.Unlock()

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

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

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) 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, error) {
	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, nil
}

func (w *World) DeleteObject(object interface{}, location engine.Location) error {
	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 nil
}

func (w *World) UpdateObject(object interface{}, old, new engine.Location) error {
	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, error) {
	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, nil
}

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, nil
}

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, nil
}

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

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

func (w *World) LocationOccupied(location engine.Location) bool {
	return w.pg.LocationOccupied(location)
}

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()
}

func (w *World) GetObjects() []interface{} {
	return w.pg.GetObjects()
}

func (w *World) ObtainIdentifier() Identifier {
	return w.identifierRegistry.Obtain()
}

func (w *World) ReleaseIdentifier(id Identifier) {
	w.identifierRegistry.Release(id)
}
<|endoftext|>


<commit_before>package valuesloader

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/valyala/fastjson"
)

func EnvironmentLoader() (ValueLoaderFunc, error) {
	return func(key string) (string, bool) {
		return os.LookupEnv(key)
	}, nil
}

func JSONLoader(data []byte) (ValueLoaderFunc, error) {
	parsed, err := fastjson.ParseBytes(data)
	if err != nil {
		return nil, err
	}
	return func(key string) (string, bool) {
		keys := strings.Split(key, ".")
		if !parsed.Exists(keys...) {
			return "", false
		}

		value := parsed.Get(keys...)
		switch value.Type() {
		case fastjson.TypeNull:
			return "", true

		case fastjson.TypeTrue:
			return "true", true

		case fastjson.TypeFalse:
			return "false", true

		case fastjson.TypeString:
			str := value.String()
			return str[1 : len(str)-1], true

		case fastjson.TypeNumber:
			if n, err := value.Int64(); err == nil {
				return strconv.FormatInt(n, 10), true
			}
			if n, err := value.Float64(); err == nil {
				return strconv.FormatFloat(n, 'f', -1, 64), true
			}
			return "", false

		default:
			return "", false
		}
	}, nil
}

func RemoteJSONLoader(url string) (ValueLoaderFunc, error) {
	res, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	data, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return JSONLoader(data)
}

func JSONFileLoader(filepath string) (ValueLoaderFunc, error) {
	file, err := os.Open(filepath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}

	return JSONLoader(data)
}

func AWSSecretsManagerLoader(secretArn string) (ValueLoaderFunc, error) {
	sess := session.New()
	sm := secretsmanager.New(sess)
	out, err := sm.GetSecretValue(&secretsmanager.GetSecretValueInput{
		SecretId: aws.String(secretArn),
	})
	if err != nil {
		return nil, err
	}
	if out == nil {
		return nil, fmt.Errorf("got unexpected nil value")
	}

	return JSONLoader([]byte(*out.SecretString))
}
<commit_msg>Fixed AWS session credentials<commit_after>package valuesloader

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/valyala/fastjson"
)

func EnvironmentLoader() (ValueLoaderFunc, error) {
	return func(key string) (string, bool) {
		return os.LookupEnv(key)
	}, nil
}

func JSONLoader(data []byte) (ValueLoaderFunc, error) {
	parsed, err := fastjson.ParseBytes(data)
	if err != nil {
		return nil, err
	}
	return func(key string) (string, bool) {
		keys := strings.Split(key, ".")
		if !parsed.Exists(keys...) {
			return "", false
		}

		value := parsed.Get(keys...)
		switch value.Type() {
		case fastjson.TypeNull:
			return "", true

		case fastjson.TypeTrue:
			return "true", true

		case fastjson.TypeFalse:
			return "false", true

		case fastjson.TypeString:
			str := value.String()
			return str[1 : len(str)-1], true

		case fastjson.TypeNumber:
			if n, err := value.Int64(); err == nil {
				return strconv.FormatInt(n, 10), true
			}
			if n, err := value.Float64(); err == nil {
				return strconv.FormatFloat(n, 'f', -1, 64), true
			}
			return "", false

		default:
			return "", false
		}
	}, nil
}

func RemoteJSONLoader(url string) (ValueLoaderFunc, error) {
	res, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	data, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	return JSONLoader(data)
}

func JSONFileLoader(filepath string) (ValueLoaderFunc, error) {
	file, err := os.Open(filepath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	data, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}

	return JSONLoader(data)
}

func AWSSecretsManagerLoader(secretArn string) (ValueLoaderFunc, error) {
	sess, err := session.NewSessionWithOptions(session.Options{
		Config: aws.Config{
			Credentials: credentials.NewEnvCredentials(),
		},
	})
	if err != nil {
		return nil, err
	}

	sm := secretsmanager.New(sess)
	out, err := sm.GetSecretValue(&secretsmanager.GetSecretValueInput{
		SecretId: aws.String(secretArn),
	})
	if err != nil {
		return nil, err
	}
	if out == nil {
		return nil, fmt.Errorf("got unexpected nil value")
	}

	return JSONLoader([]byte(*out.SecretString))
}
<|endoftext|>