<commit_before>package msg

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/viant/endly/system/cloud/ec2"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/cred"
	"strings"
	"time"
)

type awsPubSub struct {
	session *session.Session
	sqs     *sqs.SQS
	sns     *sns.SNS
	timeout time.Duration
}

func (c *awsPubSub) sendMessage(dest *Resource, message *Message) (Result, error) {
	queueURL, err := c.getQueueURL(dest.Name)
	if err != nil {
		return nil, err
	}
	input := &sqs.SendMessageInput{
		DelaySeconds:      aws.Int64(1),
		MessageAttributes: map[string]*sqs.MessageAttributeValue{},
		QueueUrl:          &queueURL,
	}

	if len(message.Attributes) > 0 {
		for k, v := range message.Attributes {
			input.MessageAttributes[k] = &sqs.MessageAttributeValue{
				DataType:    aws.String("String"),
				StringValue: aws.String(v),
			}
		}
	}
	var body = toolbox.AsString(message.Data)
	input.MessageBody = aws.String(body)
	result, err := c.sqs.SendMessage(input)
	if err != nil {
		return nil, err
	}
	return *result.MessageId, nil
}

func (c *awsPubSub) publishMessage(dest *Resource, message *Message) (Result, error) {
	topicARN, err := c.getTopicARN(dest.Name)
	if err != nil {
		return nil, err
	}
	input := &sns.PublishInput{
		MessageAttributes: map[string]*sns.MessageAttributeValue{},
		TopicArn:          aws.String(topicARN),
	}
	if len(message.Attributes) > 0 {
		for k, v := range message.Attributes {
			input.MessageAttributes[k] = &sns.MessageAttributeValue{
				DataType:    aws.String("String"),
				StringValue: aws.String(v),
			}
		}
	}
	var body = toolbox.AsString(message.Data)
	input.Message = aws.String(body)
	input.Subject = aws.String(message.Subject)
	output, err := c.sns.Publish(input)
	if err != nil {
		return nil, err
	}
	return *output.MessageId, nil
}

func (c *awsPubSub) Push(dest *Resource, message *Message) (Result, error) {
	switch dest.Type {
	case ResourceTypeTopic:
		return c.publishMessage(dest, message)
	case ResourceTypeQueue:
		return c.sendMessage(dest, message)

	}
	return nil, fmt.Errorf("unsupported resource type: %v", dest.Type)
}

func (c *awsPubSub) PullN(source *Resource, count int, nack bool) ([]*Message, error) {
	queueURL, err := c.getQueueURL(source.Name)
	if err != nil {
		return nil, err
	}
	input := &sqs.ReceiveMessageInput{
		QueueUrl: aws.String(queueURL),
		AttributeNames: aws.StringSlice([]string{
			"All",
		}),
		MaxNumberOfMessages: aws.Int64(int64(count)),
		MessageAttributeNames: aws.StringSlice([]string{
			"All",
		}),
		WaitTimeSeconds: aws.Int64(int64(c.timeout * time.Second)),
	}
	// Receive a message from the SQS queue with long polling enabled.
	output, err := c.sqs.ReceiveMessage(input)
	var result = make([]*Message, 0)
	if err != nil || len(output.Messages) == 0 {
		return result, err
	}
	for _, msg := range output.Messages {
		message := &Message{
			ID:         *msg.MessageId,
			Attributes: map[string]string{},
		}
		if msg.Body != nil {
			message.Data = *msg.Body
		}
		if len(msg.MessageAttributes) > 0 {
			for k, v := range msg.MessageAttributes {
				val := ""
				if v != nil {
					val = *v.StringValue
				}
				message.Attributes[k] = val
			}
		}

	}
	return result, nil
}

func (c *awsPubSub) createSubscription(topicURL, queueURL string) (*Resource, error) {
	input := &sns.SubscribeInput{
		Endpoint:              aws.String(queueURL),
		Protocol:              aws.String("sqs"),
		TopicArn:              aws.String(topicURL),
		ReturnSubscriptionArn: aws.Bool(true),
	}
	output, err := c.sns.Subscribe(input)
	if err != nil {
		return nil, err
	}
	return &Resource{URL: *output.SubscriptionArn}, nil
}

func (c *awsPubSub) createQueue(resource *ResourceSetup) (*Resource, error) {
	var name = resource.Name

	if resource.Recreate {
		if _, err := c.getQueueURL(resource.Name); err == nil {
			if err = c.deleteQueue(&resource.Resource); err != nil {
				return nil, fmt.Errorf("failed to delete queue: %v, %v", name, err)
			}
		}
	}

	input := &sqs.CreateQueueInput{
		QueueName:  aws.String(name),
		Attributes: map[string]*string{},
	}
	if resource.Config != nil && len(resource.Config.Attributes) > 0 {
		for k, v := range resource.Config.Attributes {
			input.Attributes[k] = aws.String(v)
		}
	}
	result, err := c.sqs.CreateQueue(input)
	if err != nil {
		return nil, err
	}
	var resultResource = &Resource{URL: *result.QueueUrl, Name: name}
	if resource.Config.Topic != nil {
		topicURL, err := c.getTopicARN(resource.Config.Topic.URL)
		if err != nil {
			return nil, err
		}
		_, err = c.createSubscription(topicURL, *result.QueueUrl)
		if err != nil {
			return nil, err
		}
	}
	return resultResource, nil
}

func (c *awsPubSub) getTopicARN(topicURL string) (string, error) {
	input := &sns.ListTopicsInput{}
	for { //TODO look into better way to get topic URL
		output, err := c.sns.ListTopics(input)
		if err != nil {
			return "", err
		}
		for _, topic := range output.Topics {
			parts := strings.Split(*topic.TopicArn, ":")
			candidate := parts[len(parts)-1]
			if candidate == topicURL {
				return *topic.TopicArn, nil
			}
		}
		input.NextToken = output.NextToken
		if output.NextToken == nil {
			break
		}
	}
	return "", fmt.Errorf("failed to lookup topic URL %v", topicURL)
}

func (c *awsPubSub) getQueueURL(queueName string) (string, error) {
	result, err := c.sqs.GetQueueUrl(&sqs.GetQueueUrlInput{
		QueueName: aws.String(queueName),
	})
	if err != nil {
		return "", fmt.Errorf("failed to lookup queue URL %v", queueName)
	}
	return *result.QueueUrl, nil
}

func (c *awsPubSub) createTopic(resource *ResourceSetup) (*Resource, error) {
	var name = resource.Name

	if resource.Recreate {
		if arn, _ := c.getTopicARN(resource.Name); arn != "" {
			if err := c.deleteTopic(&resource.Resource); err != nil {
				return nil, fmt.Errorf("failed to delete topic: %v, %v", name, err)
			}
		}
	}

	input := &sns.CreateTopicInput{
		Name: aws.String(name),
	}
	result, err := c.sns.CreateTopic(input)
	if err != nil {
		return nil, err
	}
	var resultResource = &Resource{URL: *result.TopicArn, Name: resource.Name}
	return resultResource, nil
}

func (c *awsPubSub) Create(resource *ResourceSetup) (*Resource, error) {
	switch resource.Type {
	case ResourceTypeTopic:
		return c.createTopic(resource)
	case ResourceTypeQueue:
		return c.createQueue(resource)
	}
	return nil, fmt.Errorf("unsupported resource type: %v", resource.Type)
}

func (c *awsPubSub) deleteQueue(resource *Resource) error {
	queueURL, err := c.getQueueURL(resource.Name)
	if err != nil {
		return err
	}
	_, err = c.sqs.DeleteQueue(&sqs.DeleteQueueInput{
		QueueUrl: aws.String(queueURL),
	})
	return nil
}

func (c *awsPubSub) deleteTopic(resource *Resource) error {
	queueURL, err := c.getTopicARN(resource.Name)
	if err != nil {
		return err
	}
	_, err = c.sns.DeleteTopic(&sns.DeleteTopicInput{
		TopicArn: aws.String(queueURL),
	})
	return nil
}

func (c *awsPubSub) Delete(resource *Resource) error {
	switch resource.Type {
	case ResourceTypeQueue:
		return c.deleteQueue(resource)
	case ResourceTypeTopic:
		return c.deleteTopic(resource)
	}
	return fmt.Errorf("unsupported resource type: %v", resource.Type)
}

func (c *awsPubSub) Close() error {
	return nil
}

func newAwsSqsClient(credConfig *cred.Config, timeout time.Duration) (Client, error) {
	config, err := ec2.GetAWSCredentialConfig(credConfig)
	if err != nil {
		return nil, err
	}
	var client = &awsPubSub{
		timeout: timeout,
	}
	if client.session, err = session.NewSession(config); err != nil {
		return nil, err
	}
	client.sqs = sqs.New(client.session)
	client.sns = sns.New(client.session)
	return client, nil
}
<commit_msg>updated dep<commit_after>package msg

import (
	"fmt"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sqs"
	eaws "github.com/viant/endly/system/cloud/aws"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/cred"
	"strings"
	"time"
)

type awsPubSub struct {
	session *session.Session
	sqs     *sqs.SQS
	sns     *sns.SNS
	timeout time.Duration
}

func (c *awsPubSub) sendMessage(dest *Resource, message *Message) (Result, error) {
	queueURL, err := c.getQueueURL(dest.Name)
	if err != nil {
		return nil, err
	}
	input := &sqs.SendMessageInput{
		DelaySeconds:      aws.Int64(1),
		MessageAttributes: map[string]*sqs.MessageAttributeValue{},
		QueueUrl:          &queueURL,
	}

	if len(message.Attributes) > 0 {
		for k, v := range message.Attributes {
			input.MessageAttributes[k] = &sqs.MessageAttributeValue{
				DataType:    aws.String("String"),
				StringValue: aws.String(v),
			}
		}
	}
	var body = toolbox.AsString(message.Data)
	input.MessageBody = aws.String(body)
	result, err := c.sqs.SendMessage(input)
	if err != nil {
		return nil, err
	}
	return *result.MessageId, nil
}

func (c *awsPubSub) publishMessage(dest *Resource, message *Message) (Result, error) {
	topicARN, err := c.getTopicARN(dest.Name)
	if err != nil {
		return nil, err
	}
	input := &sns.PublishInput{
		MessageAttributes: map[string]*sns.MessageAttributeValue{},
		TopicArn:          aws.String(topicARN),
	}
	if len(message.Attributes) > 0 {
		for k, v := range message.Attributes {
			input.MessageAttributes[k] = &sns.MessageAttributeValue{
				DataType:    aws.String("String"),
				StringValue: aws.String(v),
			}
		}
	}
	var body = toolbox.AsString(message.Data)
	input.Message = aws.String(body)
	input.Subject = aws.String(message.Subject)
	output, err := c.sns.Publish(input)
	if err != nil {
		return nil, err
	}
	return *output.MessageId, nil
}

func (c *awsPubSub) Push(dest *Resource, message *Message) (Result, error) {
	switch dest.Type {
	case ResourceTypeTopic:
		return c.publishMessage(dest, message)
	case ResourceTypeQueue:
		return c.sendMessage(dest, message)

	}
	return nil, fmt.Errorf("unsupported resource type: %v", dest.Type)
}

func (c *awsPubSub) PullN(source *Resource, count int, nack bool) ([]*Message, error) {
	queueURL, err := c.getQueueURL(source.Name)
	if err != nil {
		return nil, err
	}
	input := &sqs.ReceiveMessageInput{
		QueueUrl: aws.String(queueURL),
		AttributeNames: aws.StringSlice([]string{
			"All",
		}),
		MaxNumberOfMessages: aws.Int64(int64(count)),
		MessageAttributeNames: aws.StringSlice([]string{
			"All",
		}),
		WaitTimeSeconds: aws.Int64(int64(c.timeout * time.Second)),
	}
	// Receive a message from the SQS queue with long polling enabled.
	output, err := c.sqs.ReceiveMessage(input)
	var result = make([]*Message, 0)
	if err != nil || len(output.Messages) == 0 {
		return result, err
	}
	for _, msg := range output.Messages {
		message := &Message{
			ID:         *msg.MessageId,
			Attributes: map[string]string{},
		}
		if msg.Body != nil {
			message.Data = *msg.Body
		}
		if len(msg.MessageAttributes) > 0 {
			for k, v := range msg.MessageAttributes {
				val := ""
				if v != nil {
					val = *v.StringValue
				}
				message.Attributes[k] = val
			}
		}

	}
	return result, nil
}

func (c *awsPubSub) createSubscription(topicURL, queueURL string) (*Resource, error) {
	input := &sns.SubscribeInput{
		Endpoint:              aws.String(queueURL),
		Protocol:              aws.String("sqs"),
		TopicArn:              aws.String(topicURL),
		ReturnSubscriptionArn: aws.Bool(true),
	}
	output, err := c.sns.Subscribe(input)
	if err != nil {
		return nil, err
	}
	return &Resource{URL: *output.SubscriptionArn}, nil
}

func (c *awsPubSub) createQueue(resource *ResourceSetup) (*Resource, error) {
	var name = resource.Name

	if resource.Recreate {
		if _, err := c.getQueueURL(resource.Name); err == nil {
			if err = c.deleteQueue(&resource.Resource); err != nil {
				return nil, fmt.Errorf("failed to delete queue: %v, %v", name, err)
			}
		}
	}

	input := &sqs.CreateQueueInput{
		QueueName:  aws.String(name),
		Attributes: map[string]*string{},
	}
	if resource.Config != nil && len(resource.Config.Attributes) > 0 {
		for k, v := range resource.Config.Attributes {
			input.Attributes[k] = aws.String(v)
		}
	}
	result, err := c.sqs.CreateQueue(input)
	if err != nil {
		return nil, err
	}
	var resultResource = &Resource{URL: *result.QueueUrl, Name: name}
	if resource.Config.Topic != nil {
		topicURL, err := c.getTopicARN(resource.Config.Topic.URL)
		if err != nil {
			return nil, err
		}
		_, err = c.createSubscription(topicURL, *result.QueueUrl)
		if err != nil {
			return nil, err
		}
	}
	return resultResource, nil
}

func (c *awsPubSub) getTopicARN(topicURL string) (string, error) {
	input := &sns.ListTopicsInput{}
	for { //TODO look into better way to get topic URL
		output, err := c.sns.ListTopics(input)
		if err != nil {
			return "", err
		}
		for _, topic := range output.Topics {
			parts := strings.Split(*topic.TopicArn, ":")
			candidate := parts[len(parts)-1]
			if candidate == topicURL {
				return *topic.TopicArn, nil
			}
		}
		input.NextToken = output.NextToken
		if output.NextToken == nil {
			break
		}
	}
	return "", fmt.Errorf("failed to lookup topic URL %v", topicURL)
}

func (c *awsPubSub) getQueueURL(queueName string) (string, error) {
	result, err := c.sqs.GetQueueUrl(&sqs.GetQueueUrlInput{
		QueueName: aws.String(queueName),
	})
	if err != nil {
		return "", fmt.Errorf("failed to lookup queue URL %v", queueName)
	}
	return *result.QueueUrl, nil
}

func (c *awsPubSub) createTopic(resource *ResourceSetup) (*Resource, error) {
	var name = resource.Name

	if resource.Recreate {
		if arn, _ := c.getTopicARN(resource.Name); arn != "" {
			if err := c.deleteTopic(&resource.Resource); err != nil {
				return nil, fmt.Errorf("failed to delete topic: %v, %v", name, err)
			}
		}
	}

	input := &sns.CreateTopicInput{
		Name: aws.String(name),
	}
	result, err := c.sns.CreateTopic(input)
	if err != nil {
		return nil, err
	}
	var resultResource = &Resource{URL: *result.TopicArn, Name: resource.Name}
	return resultResource, nil
}

func (c *awsPubSub) Create(resource *ResourceSetup) (*Resource, error) {
	switch resource.Type {
	case ResourceTypeTopic:
		return c.createTopic(resource)
	case ResourceTypeQueue:
		return c.createQueue(resource)
	}
	return nil, fmt.Errorf("unsupported resource type: %v", resource.Type)
}

func (c *awsPubSub) deleteQueue(resource *Resource) error {
	queueURL, err := c.getQueueURL(resource.Name)
	if err != nil {
		return err
	}
	_, err = c.sqs.DeleteQueue(&sqs.DeleteQueueInput{
		QueueUrl: aws.String(queueURL),
	})
	return nil
}

func (c *awsPubSub) deleteTopic(resource *Resource) error {
	queueURL, err := c.getTopicARN(resource.Name)
	if err != nil {
		return err
	}
	_, err = c.sns.DeleteTopic(&sns.DeleteTopicInput{
		TopicArn: aws.String(queueURL),
	})
	return nil
}

func (c *awsPubSub) Delete(resource *Resource) error {
	switch resource.Type {
	case ResourceTypeQueue:
		return c.deleteQueue(resource)
	case ResourceTypeTopic:
		return c.deleteTopic(resource)
	}
	return fmt.Errorf("unsupported resource type: %v", resource.Type)
}

func (c *awsPubSub) Close() error {
	return nil
}

func newAwsSqsClient(credConfig *cred.Config, timeout time.Duration) (Client, error) {
	config, err := eaws.GetAWSCredentialConfig(credConfig)
	if err != nil {
		return nil, err
	}
	var client = &awsPubSub{
		timeout: timeout,
	}
	if client.session, err = session.NewSession(config); err != nil {
		return nil, err
	}
	client.sqs = sqs.New(client.session)
	client.sns = sns.New(client.session)
	return client, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 integration_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"runtime"
	"testing"

	"github.com/googlecloudplatform/gcsfuse/internal/canned"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestMountHelper(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type MountHelperTest struct {
	// Path to the mount(8) helper binary.
	helperPath string

	// A temporary directory into which a file system may be mounted. Removed in
	// TearDown.
	dir string
}

var _ SetUpInterface = &MountHelperTest{}
var _ TearDownInterface = &MountHelperTest{}

func init() { RegisterTestSuite(&MountHelperTest{}) }

func (t *MountHelperTest) SetUp(_ *TestInfo) {
	var err error

	// Set up the appropriate helper path.
	switch runtime.GOOS {
	case "darwin":
		t.helperPath = path.Join(gBuildDir, "sbin/mount_gcsfuse")

	case "linux":
		t.helperPath = path.Join(gBuildDir, "sbin/mount.gcsfuse")

	default:
		AddFailure("Don't know how to deal with OS: %q", runtime.GOOS)
		AbortTest()
	}

	// Set up the temporary directory.
	t.dir, err = ioutil.TempDir("", "mount_helper_test")
	AssertEq(nil, err)
}

func (t *MountHelperTest) TearDown() {
	err := os.Remove(t.dir)
	AssertEq(nil, err)
}

func (t *MountHelperTest) mountHelperCommand(args []string) (cmd *exec.Cmd) {
	cmd = exec.Command(t.helperPath)
	cmd.Args = append(cmd.Args, args...)
	cmd.Env = []string{
		fmt.Sprintf("PATH=%s", path.Join(gBuildDir, "bin")),
	}

	return
}

func (t *MountHelperTest) mount(args []string) (err error) {
	cmd := t.mountHelperCommand(args)

	output, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf("CombinedOutput: %v\nOutput:\n%s", err, output)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *MountHelperTest) BadUsage() {
	testCases := []struct {
		args           []string
		expectedOutput string
	}{
		// Too few args
		0: {
			[]string{canned.FakeBucketName},
			"two positional arguments",
		},

		// Too many args
		1: {
			[]string{canned.FakeBucketName, "a", "b"},
			"Unexpected arg 3",
		},

		// Trailing -o
		2: {
			[]string{canned.FakeBucketName, "a", "-o"},
			"Unexpected -o",
		},
	}

	// Run each test case.
	for i, tc := range testCases {
		cmd := t.mountHelperCommand(tc.args)

		output, err := cmd.CombinedOutput()
		ExpectThat(err, Error(HasSubstr("exit status")), "case %d", i)
		ExpectThat(string(output), MatchesRegexp(tc.expectedOutput), "case %d", i)
	}
}

func (t *MountHelperTest) SuccessfulMount() {
	var err error
	var fi os.FileInfo

	// Mount.
	args := []string{canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Check that the file system is available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) RelativeMountPoint() {
	var err error
	var fi os.FileInfo

	// Mount with a relative mount point.
	cmd := t.mountHelperCommand([]string{
		canned.FakeBucketName,
		path.Base(t.dir),
	})

	cmd.Dir = path.Dir(t.dir)

	output, err := cmd.CombinedOutput()
	AssertEq(nil, err, "output:\n%s", output)

	defer unmount(t.dir)

	// The file system should be available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) ReadOnlyMode() {
	var err error

	// Mount.
	args := []string{"-o", "ro", canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Writing to the file system should fail.
	err = ioutil.WriteFile(path.Join(t.dir, "blah"), []byte{}, 0400)
	ExpectThat(err, Error(HasSubstr("read-only")))
}

func (t *MountHelperTest) ExtraneousOptions() {
	var err error
	var fi os.FileInfo

	// Mount with extra junk that shouldn't be passed on.
	args := []string{
		"-o", "noauto,nouser,auto,user",
		canned.FakeBucketName,
		t.dir,
	}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Check that the file system is available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) LinuxArgumentOrder() {
	var err error

	// Linux places the options at the end.
	args := []string{canned.FakeBucketName, t.dir, "-o", "ro"}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Writing to the file system should fail.
	err = ioutil.WriteFile(path.Join(t.dir, "blah"), []byte{}, 0400)
	ExpectThat(err, Error(HasSubstr("read-only")))
}

func (t *MountHelperTest) FuseSubtype() {
	var err error
	var fi os.FileInfo

	// This test isn't relevant except on Linux.
	if runtime.GOOS != "linux" {
		return
	}

	// Mount using the tool that would be invoked by ~mount -t fuse.gcsfuse`.
	t.helperPath = path.Join(gBuildDir, "sbin/mount.fuse.gcsfuse")
	args := []string{canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Check that the file system is available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}
<commit_msg>Add integration tests for #151.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 integration_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"runtime"
	"testing"

	"github.com/googlecloudplatform/gcsfuse/internal/canned"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestMountHelper(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type MountHelperTest struct {
	// Path to the mount(8) helper binary.
	helperPath string

	// A temporary directory into which a file system may be mounted. Removed in
	// TearDown.
	dir string
}

var _ SetUpInterface = &MountHelperTest{}
var _ TearDownInterface = &MountHelperTest{}

func init() { RegisterTestSuite(&MountHelperTest{}) }

func (t *MountHelperTest) SetUp(_ *TestInfo) {
	var err error

	// Set up the appropriate helper path.
	switch runtime.GOOS {
	case "darwin":
		t.helperPath = path.Join(gBuildDir, "sbin/mount_gcsfuse")

	case "linux":
		t.helperPath = path.Join(gBuildDir, "sbin/mount.gcsfuse")

	default:
		AddFailure("Don't know how to deal with OS: %q", runtime.GOOS)
		AbortTest()
	}

	// Set up the temporary directory.
	t.dir, err = ioutil.TempDir("", "mount_helper_test")
	AssertEq(nil, err)
}

func (t *MountHelperTest) TearDown() {
	err := os.Remove(t.dir)
	AssertEq(nil, err)
}

func (t *MountHelperTest) mountHelperCommand(args []string) (cmd *exec.Cmd) {
	cmd = exec.Command(t.helperPath)
	cmd.Args = append(cmd.Args, args...)
	cmd.Env = []string{
		fmt.Sprintf("PATH=%s", path.Join(gBuildDir, "bin")),
	}

	return
}

func (t *MountHelperTest) mount(args []string) (err error) {
	cmd := t.mountHelperCommand(args)

	output, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf("CombinedOutput: %v\nOutput:\n%s", err, output)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *MountHelperTest) BadUsage() {
	testCases := []struct {
		args           []string
		expectedOutput string
	}{
		// Too few args
		0: {
			[]string{canned.FakeBucketName},
			"two positional arguments",
		},

		// Too many args
		1: {
			[]string{canned.FakeBucketName, "a", "b"},
			"Unexpected arg 3",
		},

		// Trailing -o
		2: {
			[]string{canned.FakeBucketName, "a", "-o"},
			"Unexpected -o",
		},
	}

	// Run each test case.
	for i, tc := range testCases {
		cmd := t.mountHelperCommand(tc.args)

		output, err := cmd.CombinedOutput()
		ExpectThat(err, Error(HasSubstr("exit status")), "case %d", i)
		ExpectThat(string(output), MatchesRegexp(tc.expectedOutput), "case %d", i)
	}
}

func (t *MountHelperTest) SuccessfulMount() {
	var err error
	var fi os.FileInfo

	// Mount.
	args := []string{canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Check that the file system is available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) RelativeMountPoint() {
	var err error
	var fi os.FileInfo

	// Mount with a relative mount point.
	cmd := t.mountHelperCommand([]string{
		canned.FakeBucketName,
		path.Base(t.dir),
	})

	cmd.Dir = path.Dir(t.dir)

	output, err := cmd.CombinedOutput()
	AssertEq(nil, err, "output:\n%s", output)

	defer unmount(t.dir)

	// The file system should be available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) ReadOnlyMode() {
	var err error

	// Mount.
	args := []string{"-o", "ro", canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Writing to the file system should fail.
	err = ioutil.WriteFile(path.Join(t.dir, "blah"), []byte{}, 0400)
	ExpectThat(err, Error(HasSubstr("read-only")))
}

func (t *MountHelperTest) ExtraneousOptions() {
	var err error
	var fi os.FileInfo

	// Mount with extra junk that shouldn't be passed on.
	args := []string{
		"-o", "noauto,nouser,auto,user",
		canned.FakeBucketName,
		t.dir,
	}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Check that the file system is available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) LinuxArgumentOrder() {
	var err error

	// Linux places the options at the end.
	args := []string{canned.FakeBucketName, t.dir, "-o", "ro"}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Writing to the file system should fail.
	err = ioutil.WriteFile(path.Join(t.dir, "blah"), []byte{}, 0400)
	ExpectThat(err, Error(HasSubstr("read-only")))
}

func (t *MountHelperTest) FuseSubtype() {
	var err error
	var fi os.FileInfo

	// This test isn't relevant except on Linux.
	if runtime.GOOS != "linux" {
		return
	}

	// Mount using the tool that would be invoked by ~mount -t fuse.gcsfuse`.
	t.helperPath = path.Join(gBuildDir, "sbin/mount.fuse.gcsfuse")
	args := []string{canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Check that the file system is available.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0644), fi.Mode())
	ExpectEq(len(canned.TopLevelFile_Contents), fi.Size())
}

func (t *MountHelperTest) ModeOptions() {
	var err error
	var fi os.FileInfo

	// Mount.
	args := []string{
		"-o", "dir_mode=754",
		"-o", "file_mode=612",
		canned.FakeBucketName, t.dir,
	}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// Stat the directory.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelDir))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(0754)|os.ModeDir, fi.Mode())

	// Stat the file.
	fi, err = os.Lstat(path.Join(t.dir, canned.TopLevelFile))
	AssertEq(nil, err)
	ExpectEq(os.FileMode(612)|os.ModeDir, fi.Mode())
}

func (t *MountHelperTest) ImplicitDirs() {
	var err error

	// Mount.
	args := []string{"-o", "implicit_dirs", canned.FakeBucketName, t.dir}

	err = t.mount(args)
	AssertEq(nil, err)
	defer unmount(t.dir)

	// The implicit directory should be visible.
	fi, err := os.Lstat(path.Join(t.dir, canned.ImplicitDirFile))
	AssertEq(nil, err)
	ExpectTrue(fi.IsDir())
}
<|endoftext|>


<commit_before>// Package mock contains mock implementations of different task interfaces.
package mock

import (
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/influxdata/platform"
	"github.com/influxdata/platform/task/backend"
	scheduler "github.com/influxdata/platform/task/backend"
	"go.uber.org/zap"
)

// Scheduler is a mock implementation of a task scheduler.
type Scheduler struct {
	sync.Mutex

	lastTick int64

	claims map[string]*Task
	meta   map[string]backend.StoreTaskMeta

	createChan  chan *Task
	releaseChan chan *Task

	claimError   error
	releaseError error
}

// Task is a mock implementation of a task.
type Task struct {
	Script           string
	StartExecution   int64
	ConcurrencyLimit uint8
}

func NewScheduler() *Scheduler {
	return &Scheduler{
		claims: map[string]*Task{},
		meta:   map[string]backend.StoreTaskMeta{},
	}
}

func (s *Scheduler) Tick(now int64) {
	s.Lock()
	defer s.Unlock()

	s.lastTick = now
}

func (s *Scheduler) WithLogger(l *zap.Logger) {}

func (s *Scheduler) Start(context.Context) {}

func (s *Scheduler) Stop() {}

func (s *Scheduler) ClaimTask(task *backend.StoreTask, meta *backend.StoreTaskMeta) error {
	if s.claimError != nil {
		return s.claimError
	}

	s.Lock()
	defer s.Unlock()

	_, ok := s.claims[task.ID.String()]
	if ok {
		return errors.New("task already in list")
	}
	s.meta[task.ID.String()] = *meta

	t := &Task{Script: task.Script, StartExecution: meta.LatestCompleted, ConcurrencyLimit: uint8(meta.MaxConcurrency)}

	s.claims[task.ID.String()] = t

	if s.createChan != nil {
		s.createChan <- t
	}

	return nil
}

func (s *Scheduler) ReleaseTask(taskID platform.ID) error {
	if s.releaseError != nil {
		return s.releaseError
	}

	s.Lock()
	defer s.Unlock()

	t, ok := s.claims[taskID.String()]
	if !ok {
		return errors.New("task not in list")
	}
	if s.releaseChan != nil {
		s.releaseChan <- t
	}

	delete(s.claims, taskID.String())
	delete(s.meta, taskID.String())

	return nil
}

func (s *Scheduler) TaskFor(id platform.ID) *Task {
	return s.claims[id.String()]
}

func (s *Scheduler) TaskCreateChan() <-chan *Task {
	s.createChan = make(chan *Task, 10)
	return s.createChan
}
func (s *Scheduler) TaskReleaseChan() <-chan *Task {
	s.releaseChan = make(chan *Task, 10)
	return s.releaseChan
}

// ClaimError sets an error to be returned by s.ClaimTask, if err is not nil.
func (s *Scheduler) ClaimError(err error) {
	s.claimError = err
}

// ReleaseError sets an error to be returned by s.ReleaseTask, if err is not nil.
func (s *Scheduler) ReleaseError(err error) {
	s.releaseError = err
}

// DesiredState is a mock implementation of DesiredState (used by NewScheduler).
type DesiredState struct {
	mu sync.Mutex
	// Map of stringified task ID to last ID used for run.
	runIDs map[string]uint64

	// Map of stringified, concatenated task and platform ID, to runs that have been created.
	created map[string]backend.QueuedRun

	// Map of stringified task ID to task meta.
	meta map[string]backend.StoreTaskMeta
}

var _ backend.DesiredState = (*DesiredState)(nil)

func NewDesiredState() *DesiredState {
	return &DesiredState{
		runIDs:  make(map[string]uint64),
		created: make(map[string]backend.QueuedRun),
		meta:    make(map[string]backend.StoreTaskMeta),
	}
}

// SetTaskMeta sets the task meta for the given task ID.
// SetTaskMeta must be called before CreateNextRun, for a given task ID.
func (d *DesiredState) SetTaskMeta(taskID platform.ID, meta backend.StoreTaskMeta) {
	d.mu.Lock()
	defer d.mu.Unlock()

	d.meta[taskID.String()] = meta
}

// CreateNextRun creates the next run for the given task.
// Refer to the documentation for SetTaskPeriod to understand how the times are determined.
func (d *DesiredState) CreateNextRun(_ context.Context, taskID platform.ID, now int64) (backend.RunCreation, error) {
	d.mu.Lock()
	defer d.mu.Unlock()

	tid := taskID.String()

	meta, ok := d.meta[tid]
	if !ok {
		panic(fmt.Sprintf("meta not set for task with ID %s", tid))
	}

	makeID := func() (platform.ID, error) {
		d.runIDs[tid]++
		runID := make([]byte, 4)
		binary.BigEndian.PutUint32(runID, d.runIDs[tid])
		return platform.ID(runID), nil
	}

	rc, err := meta.CreateNextRun(now, makeID)
	if err != nil {
		return backend.RunCreation{}, err
	}
	d.meta[tid] = meta
	rc.Created.TaskID = append([]byte(nil), taskID...)
	d.created[tid+rc.Created.RunID.String()] = rc.Created
	return rc, nil
}

func (d *DesiredState) FinishRun(_ context.Context, taskID, runID platform.ID) error {
	d.mu.Lock()
	defer d.mu.Unlock()

	tid := taskID.String()
	rid := runID.String()
	m := d.meta[tid]
	if !m.FinishRun(runID) {
		var knownIDs []string
		for _, r := range m.CurrentlyRunning {
			knownIDs = append(knownIDs, platform.ID(r.RunID).String())
		}
		return fmt.Errorf("unknown run ID %s; known run IDs: %s", rid, strings.Join(knownIDs, ", "))
	}
	d.meta[tid] = m
	delete(d.created, tid+rid)
	return nil
}

func (d *DesiredState) CreatedFor(taskID platform.ID) []backend.QueuedRun {
	d.mu.Lock()
	defer d.mu.Unlock()

	var qrs []backend.QueuedRun
	for _, qr := range d.created {
		if qr.TaskID == taskID {
			qrs = append(qrs, qr)
		}
	}

	return qrs
}

// PollForNumberCreated blocks for a small amount of time waiting for exactly the given count of created runs for the given task ID.
// If the expected number isn't found in time, it returns an error.
//
// Because the scheduler and executor do a lot of state changes asynchronously, this is useful in test.
func (d *DesiredState) PollForNumberCreated(taskID platform.ID, count int) ([]scheduler.QueuedRun, error) {
	const numAttempts = 50
	actualCount := 0
	var created []scheduler.QueuedRun
	for i := 0; i < numAttempts; i++ {
		time.Sleep(2 * time.Millisecond) // we sleep even on first so it becomes more likely that we catch when too many are produced.
		created = d.CreatedFor(taskID)
		actualCount = len(created)
		if actualCount == count {
			return created, nil
		}
	}
	return created, fmt.Errorf("did not see count of %d created task(s) for ID %s in time, instead saw %d", count, taskID.String(), actualCount) // we return created anyways, to make it easier to debug
}

type Executor struct {
	mu sync.Mutex

	// Map of stringified, concatenated task and run ID, to runs that have begun execution but have not finished.
	running map[string]*RunPromise

	// Map of stringified, concatenated task and run ID, to results of runs that have executed and completed.
	finished map[string]backend.RunResult
}

var _ backend.Executor = (*Executor)(nil)

func NewExecutor() *Executor {
	return &Executor{
		running:  make(map[string]*RunPromise),
		finished: make(map[string]backend.RunResult),
	}
}

func (e *Executor) Execute(_ context.Context, run backend.QueuedRun) (backend.RunPromise, error) {
	rp := NewRunPromise(run)

	id := run.TaskID.String() + run.RunID.String()
	e.mu.Lock()
	e.running[id] = rp
	e.mu.Unlock()
	go func() {
		res, _ := rp.Wait()
		e.mu.Lock()
		delete(e.running, id)
		e.finished[id] = res
		e.mu.Unlock()
	}()
	return rp, nil
}

func (e *Executor) WithLogger(l *zap.Logger) {}

// RunningFor returns the run promises for the given task.
func (e *Executor) RunningFor(taskID platform.ID) []*RunPromise {
	e.mu.Lock()
	defer e.mu.Unlock()

	var rps []*RunPromise
	for _, rp := range e.running {
		if rp.Run().TaskID == taskID {
			rps = append(rps, rp)
		}
	}

	return rps
}

// PollForNumberRunning blocks for a small amount of time waiting for exactly the given count of active runs for the given task ID.
// If the expected number isn't found in time, it returns an error.
//
// Because the scheduler and executor do a lot of state changes asynchronously, this is useful in test.
func (e *Executor) PollForNumberRunning(taskID platform.ID, count int) ([]*RunPromise, error) {
	const numAttempts = 20
	var running []*RunPromise
	for i := 0; i < numAttempts; i++ {
		if i > 0 {
			time.Sleep(10 * time.Millisecond)
		}
		running = e.RunningFor(taskID)
		if len(running) == count {
			return running, nil
		}
	}
	return nil, fmt.Errorf("did not see count of %d running task(s) for ID %s in time; last count was %d", count, taskID.String(), len(running))
}

// RunPromise is a mock RunPromise.
type RunPromise struct {
	qr backend.QueuedRun

	setResultOnce sync.Once

	mu  sync.Mutex
	res backend.RunResult
	err error
}

var _ backend.RunPromise = (*RunPromise)(nil)

func NewRunPromise(qr backend.QueuedRun) *RunPromise {
	p := &RunPromise{
		qr: qr,
	}
	p.mu.Lock() // Locked so calls to Wait will block until setResultOnce is called.
	return p
}

func (p *RunPromise) Run() backend.QueuedRun {
	return p.qr
}

func (p *RunPromise) Wait() (backend.RunResult, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.res, p.err
}

func (p *RunPromise) Cancel() {
	p.Finish(nil, backend.ErrRunCanceled)
}

// Finish unblocks any call to Wait, to return r and err.
// Only the first call to Finish has any effect.
func (p *RunPromise) Finish(r backend.RunResult, err error) {
	p.setResultOnce.Do(func() {
		p.res, p.err = r, err
		p.mu.Unlock()
	})
}

// RunResult is a mock implementation of RunResult.
type RunResult struct {
	err         error
	isRetryable bool
}

var _ backend.RunResult = (*RunResult)(nil)

func NewRunResult(err error, isRetryable bool) *RunResult {
	return &RunResult{err: err, isRetryable: isRetryable}
}

func (rr *RunResult) Err() error {
	return rr.err
}

func (rr *RunResult) IsRetryable() bool {
	return rr.isRetryable
}
<commit_msg>fix(task/mock): porting to uint64 IDs<commit_after>// Package mock contains mock implementations of different task interfaces.
package mock

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/influxdata/platform"
	"github.com/influxdata/platform/task/backend"
	scheduler "github.com/influxdata/platform/task/backend"
	"go.uber.org/zap"
)

// Scheduler is a mock implementation of a task scheduler.
type Scheduler struct {
	sync.Mutex

	lastTick int64

	claims map[string]*Task
	meta   map[string]backend.StoreTaskMeta

	createChan  chan *Task
	releaseChan chan *Task

	claimError   error
	releaseError error
}

// Task is a mock implementation of a task.
type Task struct {
	Script           string
	StartExecution   int64
	ConcurrencyLimit uint8
}

func NewScheduler() *Scheduler {
	return &Scheduler{
		claims: map[string]*Task{},
		meta:   map[string]backend.StoreTaskMeta{},
	}
}

func (s *Scheduler) Tick(now int64) {
	s.Lock()
	defer s.Unlock()

	s.lastTick = now
}

func (s *Scheduler) WithLogger(l *zap.Logger) {}

func (s *Scheduler) Start(context.Context) {}

func (s *Scheduler) Stop() {}

func (s *Scheduler) ClaimTask(task *backend.StoreTask, meta *backend.StoreTaskMeta) error {
	if s.claimError != nil {
		return s.claimError
	}

	s.Lock()
	defer s.Unlock()

	_, ok := s.claims[task.ID.String()]
	if ok {
		return errors.New("task already in list")
	}
	s.meta[task.ID.String()] = *meta

	t := &Task{Script: task.Script, StartExecution: meta.LatestCompleted, ConcurrencyLimit: uint8(meta.MaxConcurrency)}

	s.claims[task.ID.String()] = t

	if s.createChan != nil {
		s.createChan <- t
	}

	return nil
}

func (s *Scheduler) ReleaseTask(taskID platform.ID) error {
	if s.releaseError != nil {
		return s.releaseError
	}

	s.Lock()
	defer s.Unlock()

	t, ok := s.claims[taskID.String()]
	if !ok {
		return errors.New("task not in list")
	}
	if s.releaseChan != nil {
		s.releaseChan <- t
	}

	delete(s.claims, taskID.String())
	delete(s.meta, taskID.String())

	return nil
}

func (s *Scheduler) TaskFor(id platform.ID) *Task {
	return s.claims[id.String()]
}

func (s *Scheduler) TaskCreateChan() <-chan *Task {
	s.createChan = make(chan *Task, 10)
	return s.createChan
}
func (s *Scheduler) TaskReleaseChan() <-chan *Task {
	s.releaseChan = make(chan *Task, 10)
	return s.releaseChan
}

// ClaimError sets an error to be returned by s.ClaimTask, if err is not nil.
func (s *Scheduler) ClaimError(err error) {
	s.claimError = err
}

// ReleaseError sets an error to be returned by s.ReleaseTask, if err is not nil.
func (s *Scheduler) ReleaseError(err error) {
	s.releaseError = err
}

// DesiredState is a mock implementation of DesiredState (used by NewScheduler).
type DesiredState struct {
	mu sync.Mutex
	// Map of stringified task ID to last ID used for run.
	runIDs map[string]uint64

	// Map of stringified, concatenated task and platform ID, to runs that have been created.
	created map[string]backend.QueuedRun

	// Map of stringified task ID to task meta.
	meta map[string]backend.StoreTaskMeta
}

var _ backend.DesiredState = (*DesiredState)(nil)

func NewDesiredState() *DesiredState {
	return &DesiredState{
		runIDs:  make(map[string]uint64),
		created: make(map[string]backend.QueuedRun),
		meta:    make(map[string]backend.StoreTaskMeta),
	}
}

// SetTaskMeta sets the task meta for the given task ID.
// SetTaskMeta must be called before CreateNextRun, for a given task ID.
func (d *DesiredState) SetTaskMeta(taskID platform.ID, meta backend.StoreTaskMeta) {
	d.mu.Lock()
	defer d.mu.Unlock()

	d.meta[taskID.String()] = meta
}

// CreateNextRun creates the next run for the given task.
// Refer to the documentation for SetTaskPeriod to understand how the times are determined.
func (d *DesiredState) CreateNextRun(_ context.Context, taskID platform.ID, now int64) (backend.RunCreation, error) {
	d.mu.Lock()
	defer d.mu.Unlock()

	tid := taskID.String()

	meta, ok := d.meta[tid]
	if !ok {
		panic(fmt.Sprintf("meta not set for task with ID %s", tid))
	}

	makeID := func() (platform.ID, error) {
		d.runIDs[tid]++
		runID := platform.ID(d.runIDs[tid])
		return runID, nil
	}

	rc, err := meta.CreateNextRun(now, makeID)
	if err != nil {
		return backend.RunCreation{}, err
	}
	d.meta[tid] = meta
	rc.Created.TaskID = taskID
	d.created[tid+rc.Created.RunID.String()] = rc.Created
	return rc, nil
}

func (d *DesiredState) FinishRun(_ context.Context, taskID, runID platform.ID) error {
	d.mu.Lock()
	defer d.mu.Unlock()

	tid := taskID.String()
	rid := runID.String()
	m := d.meta[tid]
	if !m.FinishRun(runID) {
		var knownIDs []string
		for _, r := range m.CurrentlyRunning {
			knownIDs = append(knownIDs, platform.ID(r.RunID).String())
		}
		return fmt.Errorf("unknown run ID %s; known run IDs: %s", rid, strings.Join(knownIDs, ", "))
	}
	d.meta[tid] = m
	delete(d.created, tid+rid)
	return nil
}

func (d *DesiredState) CreatedFor(taskID platform.ID) []backend.QueuedRun {
	d.mu.Lock()
	defer d.mu.Unlock()

	var qrs []backend.QueuedRun
	for _, qr := range d.created {
		if qr.TaskID == taskID {
			qrs = append(qrs, qr)
		}
	}

	return qrs
}

// PollForNumberCreated blocks for a small amount of time waiting for exactly the given count of created runs for the given task ID.
// If the expected number isn't found in time, it returns an error.
//
// Because the scheduler and executor do a lot of state changes asynchronously, this is useful in test.
func (d *DesiredState) PollForNumberCreated(taskID platform.ID, count int) ([]scheduler.QueuedRun, error) {
	const numAttempts = 50
	actualCount := 0
	var created []scheduler.QueuedRun
	for i := 0; i < numAttempts; i++ {
		time.Sleep(2 * time.Millisecond) // we sleep even on first so it becomes more likely that we catch when too many are produced.
		created = d.CreatedFor(taskID)
		actualCount = len(created)
		if actualCount == count {
			return created, nil
		}
	}
	return created, fmt.Errorf("did not see count of %d created task(s) for ID %s in time, instead saw %d", count, taskID.String(), actualCount) // we return created anyways, to make it easier to debug
}

type Executor struct {
	mu sync.Mutex

	// Map of stringified, concatenated task and run ID, to runs that have begun execution but have not finished.
	running map[string]*RunPromise

	// Map of stringified, concatenated task and run ID, to results of runs that have executed and completed.
	finished map[string]backend.RunResult
}

var _ backend.Executor = (*Executor)(nil)

func NewExecutor() *Executor {
	return &Executor{
		running:  make(map[string]*RunPromise),
		finished: make(map[string]backend.RunResult),
	}
}

func (e *Executor) Execute(_ context.Context, run backend.QueuedRun) (backend.RunPromise, error) {
	rp := NewRunPromise(run)

	id := run.TaskID.String() + run.RunID.String()
	e.mu.Lock()
	e.running[id] = rp
	e.mu.Unlock()
	go func() {
		res, _ := rp.Wait()
		e.mu.Lock()
		delete(e.running, id)
		e.finished[id] = res
		e.mu.Unlock()
	}()
	return rp, nil
}

func (e *Executor) WithLogger(l *zap.Logger) {}

// RunningFor returns the run promises for the given task.
func (e *Executor) RunningFor(taskID platform.ID) []*RunPromise {
	e.mu.Lock()
	defer e.mu.Unlock()

	var rps []*RunPromise
	for _, rp := range e.running {
		if rp.Run().TaskID == taskID {
			rps = append(rps, rp)
		}
	}

	return rps
}

// PollForNumberRunning blocks for a small amount of time waiting for exactly the given count of active runs for the given task ID.
// If the expected number isn't found in time, it returns an error.
//
// Because the scheduler and executor do a lot of state changes asynchronously, this is useful in test.
func (e *Executor) PollForNumberRunning(taskID platform.ID, count int) ([]*RunPromise, error) {
	const numAttempts = 20
	var running []*RunPromise
	for i := 0; i < numAttempts; i++ {
		if i > 0 {
			time.Sleep(10 * time.Millisecond)
		}
		running = e.RunningFor(taskID)
		if len(running) == count {
			return running, nil
		}
	}
	return nil, fmt.Errorf("did not see count of %d running task(s) for ID %s in time; last count was %d", count, taskID.String(), len(running))
}

// RunPromise is a mock RunPromise.
type RunPromise struct {
	qr backend.QueuedRun

	setResultOnce sync.Once

	mu  sync.Mutex
	res backend.RunResult
	err error
}

var _ backend.RunPromise = (*RunPromise)(nil)

func NewRunPromise(qr backend.QueuedRun) *RunPromise {
	p := &RunPromise{
		qr: qr,
	}
	p.mu.Lock() // Locked so calls to Wait will block until setResultOnce is called.
	return p
}

func (p *RunPromise) Run() backend.QueuedRun {
	return p.qr
}

func (p *RunPromise) Wait() (backend.RunResult, error) {
	p.mu.Lock()
	defer p.mu.Unlock()
	return p.res, p.err
}

func (p *RunPromise) Cancel() {
	p.Finish(nil, backend.ErrRunCanceled)
}

// Finish unblocks any call to Wait, to return r and err.
// Only the first call to Finish has any effect.
func (p *RunPromise) Finish(r backend.RunResult, err error) {
	p.setResultOnce.Do(func() {
		p.res, p.err = r, err
		p.mu.Unlock()
	})
}

// RunResult is a mock implementation of RunResult.
type RunResult struct {
	err         error
	isRetryable bool
}

var _ backend.RunResult = (*RunResult)(nil)

func NewRunResult(err error, isRetryable bool) *RunResult {
	return &RunResult{err: err, isRetryable: isRetryable}
}

func (rr *RunResult) Err() error {
	return rr.err
}

func (rr *RunResult) IsRetryable() bool {
	return rr.isRetryable
}
<|endoftext|>


<commit_before>package comet

import (
	"context"
	"math/rand"
	"time"

	logic "github.com/Terry-Mao/goim/api/logic/grpc"
	"github.com/Terry-Mao/goim/internal/comet/conf"
	log "github.com/golang/glog"
	"github.com/zhenjl/cityhash"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
)

var (
	maxInt = 1<<31 - 1
	// grpc options
	grpcKeepAliveTime    = time.Duration(10) * time.Second
	grpcKeepAliveTimeout = time.Duration(3) * time.Second
	grpcBackoffMaxDelay  = time.Duration(3) * time.Second
	grpcMaxSendMsgSize   = 1 << 24
	grpcMaxCallMsgSize   = 1 << 24
)

const (
	clientHeartbeat       = time.Second * 90
	minSrvHeartbeatSecond = time.Minute * 10
	maxSrvHeartbeatSecond = time.Minute * 30
	// grpc options
	grpcInitialWindowSize     = 1 << 24
	grpcInitialConnWindowSize = 1 << 24
)

// Server is comet server.
type Server struct {
	c         *conf.Config
	round     *Round    // accept round store
	buckets   []*Bucket // subkey bucket
	bucketIdx uint32

	serverID  string
	rpcClient logic.LogicClient
}

func newLogicClient(c *conf.RPCClient) logic.LogicClient {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.Dial))
	defer cancel()
	conn, err := grpc.DialContext(ctx, "discovery://default/goim.logic",
		[]grpc.DialOption{
			grpc.WithInsecure(),
			grpc.WithInitialWindowSize(grpcInitialWindowSize),
			grpc.WithInitialConnWindowSize(grpcInitialConnWindowSize),
			grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)),
			grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)),
			grpc.WithBackoffMaxDelay(grpcBackoffMaxDelay),
			grpc.WithKeepaliveParams(keepalive.ClientParameters{
				Time:                grpcKeepAliveTime,
				Timeout:             grpcKeepAliveTimeout,
				PermitWithoutStream: true,
			}),
		}...)
	if err != nil {
		panic(err)
	}
	return logic.NewLogicClient(conn)
}

// NewServer returns a new Server.
func NewServer(c *conf.Config) *Server {
	s := &Server{
		c:         c,
		round:     NewRound(c),
		rpcClient: newLogicClient(c.RPCClient),
	}
	// init bucket
	s.buckets = make([]*Bucket, c.Bucket.Size)
	s.bucketIdx = uint32(c.Bucket.Size)
	for i := 0; i < c.Bucket.Size; i++ {
		s.buckets[i] = NewBucket(c.Bucket)
	}
	s.serverID = c.Env.Host
	go s.onlineproc()
	return s
}

// Buckets return all buckets.
func (s *Server) Buckets() []*Bucket {
	return s.buckets
}

// Bucket get the bucket by subkey.
func (s *Server) Bucket(subKey string) *Bucket {
	idx := cityhash.CityHash32([]byte(subKey), uint32(len(subKey))) % s.bucketIdx
	if conf.Conf.Debug {
		log.Infof("%s hit channel bucket index: %d use cityhash", subKey, idx)
	}
	return s.buckets[idx]
}

// RandServerHearbeat rand server heartbeat.
func (s *Server) RandServerHearbeat() time.Duration {
	return (minSrvHeartbeatSecond + time.Duration(rand.Intn(int(maxSrvHeartbeatSecond-minSrvHeartbeatSecond))))
}

// Close close the server.
func (s *Server) Close() (err error) {
	return
}

func (s *Server) onlineproc() {
	for {
		var (
			allRoomsCount map[string]int32
			err           error
		)
		roomCount := make(map[string]int32)
		for _, bucket := range s.buckets {
			for roomID, count := range bucket.RoomsCount() {
				roomCount[roomID] += count
			}
		}
		if allRoomsCount, err = s.RenewOnline(context.Background(), s.serverID, roomCount); err != nil {
			time.Sleep(time.Duration(s.c.OnlineTick))
			continue
		}
		for _, bucket := range s.buckets {
			bucket.UpRoomsCount(allRoomsCount)
		}
		time.Sleep(time.Duration(s.c.OnlineTick))
	}
}
<commit_msg>add grpc balancer<commit_after>package comet

import (
	"context"
	"math/rand"
	"time"

	logic "github.com/Terry-Mao/goim/api/logic/grpc"
	"github.com/Terry-Mao/goim/internal/comet/conf"
	log "github.com/golang/glog"
	"github.com/zhenjl/cityhash"
	"google.golang.org/grpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/keepalive"
)

var (
	maxInt = 1<<31 - 1
	// grpc options
	grpcKeepAliveTime    = time.Duration(10) * time.Second
	grpcKeepAliveTimeout = time.Duration(3) * time.Second
	grpcBackoffMaxDelay  = time.Duration(3) * time.Second
	grpcMaxSendMsgSize   = 1 << 24
	grpcMaxCallMsgSize   = 1 << 24
)

const (
	clientHeartbeat       = time.Second * 90
	minSrvHeartbeatSecond = time.Minute * 10
	maxSrvHeartbeatSecond = time.Minute * 30
	// grpc options
	grpcInitialWindowSize     = 1 << 24
	grpcInitialConnWindowSize = 1 << 24
)

// Server is comet server.
type Server struct {
	c         *conf.Config
	round     *Round    // accept round store
	buckets   []*Bucket // subkey bucket
	bucketIdx uint32

	serverID  string
	rpcClient logic.LogicClient
}

func newLogicClient(c *conf.RPCClient) logic.LogicClient {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.Dial))
	defer cancel()
	conn, err := grpc.DialContext(ctx, "discovery://default/goim.logic",
		[]grpc.DialOption{
			grpc.WithInsecure(),
			grpc.WithInitialWindowSize(grpcInitialWindowSize),
			grpc.WithInitialConnWindowSize(grpcInitialConnWindowSize),
			grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcMaxCallMsgSize)),
			grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(grpcMaxSendMsgSize)),
			grpc.WithBackoffMaxDelay(grpcBackoffMaxDelay),
			grpc.WithKeepaliveParams(keepalive.ClientParameters{
				Time:                grpcKeepAliveTime,
				Timeout:             grpcKeepAliveTimeout,
				PermitWithoutStream: true,
			}),
			grpc.WithBalancerName(roundrobin.Name),
		}...)
	if err != nil {
		panic(err)
	}
	return logic.NewLogicClient(conn)
}

// NewServer returns a new Server.
func NewServer(c *conf.Config) *Server {
	s := &Server{
		c:         c,
		round:     NewRound(c),
		rpcClient: newLogicClient(c.RPCClient),
	}
	// init bucket
	s.buckets = make([]*Bucket, c.Bucket.Size)
	s.bucketIdx = uint32(c.Bucket.Size)
	for i := 0; i < c.Bucket.Size; i++ {
		s.buckets[i] = NewBucket(c.Bucket)
	}
	s.serverID = c.Env.Host
	go s.onlineproc()
	return s
}

// Buckets return all buckets.
func (s *Server) Buckets() []*Bucket {
	return s.buckets
}

// Bucket get the bucket by subkey.
func (s *Server) Bucket(subKey string) *Bucket {
	idx := cityhash.CityHash32([]byte(subKey), uint32(len(subKey))) % s.bucketIdx
	if conf.Conf.Debug {
		log.Infof("%s hit channel bucket index: %d use cityhash", subKey, idx)
	}
	return s.buckets[idx]
}

// RandServerHearbeat rand server heartbeat.
func (s *Server) RandServerHearbeat() time.Duration {
	return (minSrvHeartbeatSecond + time.Duration(rand.Intn(int(maxSrvHeartbeatSecond-minSrvHeartbeatSecond))))
}

// Close close the server.
func (s *Server) Close() (err error) {
	return
}

func (s *Server) onlineproc() {
	for {
		var (
			allRoomsCount map[string]int32
			err           error
		)
		roomCount := make(map[string]int32)
		for _, bucket := range s.buckets {
			for roomID, count := range bucket.RoomsCount() {
				roomCount[roomID] += count
			}
		}
		if allRoomsCount, err = s.RenewOnline(context.Background(), s.serverID, roomCount); err != nil {
			time.Sleep(time.Duration(s.c.OnlineTick))
			continue
		}
		for _, bucket := range s.buckets {
			bucket.UpRoomsCount(allRoomsCount)
		}
		time.Sleep(time.Duration(s.c.OnlineTick))
	}
}
<|endoftext|>


<commit_before>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package plugins

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/circonus-labs/circonus-agent/internal/builtins"
	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/maier/go-appstats"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
)

// Scan the plugin directory for new/updated plugins
func (p *Plugins) Scan(b *builtins.Builtins) error {
	p.Lock()
	defer p.Unlock()

	if p.pluginDir == "" {
		return nil
	}

	// initialRun fires each plugin one time. Unlike 'Run' it does
	// not wait for plugins to finish this will provides:
	//
	// 1. an initial seeding of results
	// 2. starts any long running plugins without blocking
	//
	initialRun := func() error {
		for id, plug := range p.active {
			p.logger.Debug().
				Str("plugin", id).
				Msg("Initializing")
			go plug.exec()
		}
		return nil
	}

	// only applicable if dynamic reloading implemented
	// if err := p.Stop(); err != nil {
	// 	return errors.Wrap(err, "stopping plugin(s)")
	// }

	if err := p.scanPluginDirectory(b); err != nil {
		return errors.Wrap(err, "plugin directory scan")
	}

	if err := initialRun(); err != nil {
		return errors.Wrap(err, "initializing plugin(s)")
	}

	return nil
}

// scanPluginDirectory finds and loads plugins
func (p *Plugins) scanPluginDirectory(b *builtins.Builtins) error {
	if p.pluginDir == "" {
		return errors.New("invalid plugin directory (none)")
	}

	p.logger.Info().
		Str("dir", p.pluginDir).
		Msg("Scanning plugin directory")

	f, err := os.Open(p.pluginDir)
	if err != nil {
		return errors.Wrap(err, "open plugin directory")
	}

	defer f.Close()

	files, err := f.Readdir(-1)
	if err != nil {
		return errors.Wrap(err, "reading plugin directory")
	}

	ttlRx, err := regexp.Compile(`_ttl(.+)$`)
	if err != nil {
		return errors.Wrap(err, "compiling ttl regex")
	}
	ttlUnitRx, err := regexp.Compile(`(ms|s|m|h)$`)
	if err != nil {
		return errors.Wrap(err, "compiling ttl unit regex")
	}

	for _, fi := range files {
		fileName := fi.Name()

		p.logger.Debug().
			Str("path", filepath.Join(p.pluginDir, fileName)).
			Msg("checking plugin directory entry")

		if fi.IsDir() {
			p.logger.Debug().
				Str("file", fileName).
				Msg("directory, ignoring")
			continue
		}

		fileBase := fileName
		fileExt := filepath.Ext(fileName)

		if fileExt != "" {
			fileBase = strings.Replace(fileName, fileExt, "", -1)
		}

		if fileBase == "" || fileExt == "" {
			p.logger.Debug().
				Str("file", fileName).
				Msg("invalid file name format, ignoring")
			continue
		}

		if fileExt == ".conf" || fileExt == ".json" {
			p.logger.Debug().
				Str("file", fileName).
				Msg("config file, ignoring")
			continue
		}

		if _, reserved := p.reservedNames[fileBase]; reserved {
			p.logger.Warn().
				Str("file", fileName).
				Msg("reserved plugin name, ignoring")
			continue
		}

		var cmdName string

		switch mode := fi.Mode(); {
		case mode.IsRegular():
			cmdName = filepath.Join(p.pluginDir, fi.Name())
		case mode&os.ModeSymlink != 0:
			resolvedSymlink, err := filepath.EvalSymlinks(filepath.Join(p.pluginDir, fi.Name()))
			if err != nil {
				p.logger.Warn().
					Err(err).
					Str("file", fi.Name()).
					Msg("Error resolving symlink, ignoring")
				continue
			}
			cmdName = resolvedSymlink
		default:
			p.logger.Debug().
				Str("file", fileName).
				Msg("not a regular file or symlink, ignoring")
			continue // just ignore it
		}

		if perm := fi.Mode().Perm() & 0111; perm != 73 {
			p.logger.Warn().
				Str("file", cmdName).
				Str("perms", fmt.Sprintf("%q", fi.Mode().Perm())).
				Msg("executable bit not set, ignoring")
			continue
		}

		if b != nil && b.IsBuiltin(fileBase) {
			p.logger.Warn().Str("id", fileBase).Msg("Builtin collector already enabled, skipping plugin")
			continue
		}

		var cfg map[string][]string

		// check for config file
		cfgFile := filepath.Join(p.pluginDir, fmt.Sprintf("%s.json", fileBase))
		if data, err := ioutil.ReadFile(cfgFile); err != nil {
			if !os.IsNotExist(err) {
				p.logger.Warn().
					Err(err).
					Str("config", cfgFile).
					Str("plugin", fileBase).Msg("plugin config")
			}
		} else {
			if len(data) > 0 {
				err := json.Unmarshal(data, &cfg)
				if err != nil {
					p.logger.Warn().
						Err(err).
						Str("config", cfgFile).
						Str("plugin", fileBase).
						Str("data", string(data)).
						Msg("parsing config")
				}

				p.logger.Debug().
					Str("config", fmt.Sprintf("%+v", cfg)).
					Msg("loaded plugin config")
			}
		}

		// parse fileBase for _ttl(.+)
		matches := ttlRx.FindAllStringSubmatch(fileBase, -1)
		var runTTL time.Duration
		if len(matches) > 0 && len(matches[0]) > 1 {
			ttl := matches[0][1]
			if ttl != "" {
				if !ttlUnitRx.MatchString(ttl) {
					ttl += viper.GetString(config.KeyPluginTTLUnits)
				}

				if d, err := time.ParseDuration(ttl); err != nil {
					p.logger.Warn().Err(err).Str("ttl", ttl).Msg("parsing plugin ttl, ignoring ttl")
				} else {
					runTTL = d
				}
			}
		}

		if cfg == nil {
			plug, ok := p.active[fileBase]
			if !ok {
				p.active[fileBase] = &plugin{
					ctx:    p.ctx,
					id:     fileBase,
					name:   fileBase,
					logger: p.logger.With().Str("plugin", fileBase).Logger(),
					runDir: p.pluginDir,
					runTTL: runTTL,
				}
				plug = p.active[fileBase]
			}

			appstats.MapIncrementInt("plugins", "total")
			plug.command = cmdName
			p.logger.Info().
				Str("id", fileBase).
				Str("cmd", cmdName).
				Msg("Activating plugin")

		} else {
			for inst, args := range cfg {
				pluginName := fmt.Sprintf("%s`%s", fileBase, inst)
				plug, ok := p.active[pluginName]
				if !ok {
					p.active[pluginName] = &plugin{
						ctx:          p.ctx,
						id:           fileBase,
						instanceID:   inst,
						instanceArgs: args,
						name:         pluginName,
						logger:       p.logger.With().Str("plugin", pluginName).Logger(),
						runDir:       p.pluginDir,
						runTTL:       runTTL,
					}
					plug = p.active[pluginName]
				}

				appstats.MapIncrementInt("plugins", "total")
				plug.command = cmdName
				p.logger.Info().
					Str("id", pluginName).
					Str("cmd", cmdName).
					Msg("Activating plugin")

			}
		}
	}

	if len(p.active) == 0 {
		return errors.New("No active plugins found")
	}

	return nil
}
<commit_msg>fix: skip exec perm check for windows<commit_after>// Copyright © 2017 Circonus, Inc. <support@circonus.com>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package plugins

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"

	"github.com/circonus-labs/circonus-agent/internal/builtins"
	"github.com/circonus-labs/circonus-agent/internal/config"
	"github.com/maier/go-appstats"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
)

// Scan the plugin directory for new/updated plugins
func (p *Plugins) Scan(b *builtins.Builtins) error {
	p.Lock()
	defer p.Unlock()

	if p.pluginDir == "" {
		return nil
	}

	// initialRun fires each plugin one time. Unlike 'Run' it does
	// not wait for plugins to finish this will provides:
	//
	// 1. an initial seeding of results
	// 2. starts any long running plugins without blocking
	//
	initialRun := func() error {
		for id, plug := range p.active {
			p.logger.Debug().
				Str("plugin", id).
				Msg("Initializing")
			go plug.exec()
		}
		return nil
	}

	// only applicable if dynamic reloading implemented
	// if err := p.Stop(); err != nil {
	// 	return errors.Wrap(err, "stopping plugin(s)")
	// }

	if err := p.scanPluginDirectory(b); err != nil {
		return errors.Wrap(err, "plugin directory scan")
	}

	if err := initialRun(); err != nil {
		return errors.Wrap(err, "initializing plugin(s)")
	}

	return nil
}

// scanPluginDirectory finds and loads plugins
func (p *Plugins) scanPluginDirectory(b *builtins.Builtins) error {
	if p.pluginDir == "" {
		return errors.New("invalid plugin directory (none)")
	}

	p.logger.Info().
		Str("dir", p.pluginDir).
		Msg("Scanning plugin directory")

	f, err := os.Open(p.pluginDir)
	if err != nil {
		return errors.Wrap(err, "open plugin directory")
	}

	defer f.Close()

	files, err := f.Readdir(-1)
	if err != nil {
		return errors.Wrap(err, "reading plugin directory")
	}

	ttlRx, err := regexp.Compile(`_ttl(.+)$`)
	if err != nil {
		return errors.Wrap(err, "compiling ttl regex")
	}
	ttlUnitRx, err := regexp.Compile(`(ms|s|m|h)$`)
	if err != nil {
		return errors.Wrap(err, "compiling ttl unit regex")
	}

	for _, fi := range files {
		fileName := fi.Name()

		p.logger.Debug().
			Str("path", filepath.Join(p.pluginDir, fileName)).
			Msg("checking plugin directory entry")

		if fi.IsDir() {
			p.logger.Debug().
				Str("file", fileName).
				Msg("directory, ignoring")
			continue
		}

		fileBase := fileName
		fileExt := filepath.Ext(fileName)

		if fileExt != "" {
			fileBase = strings.Replace(fileName, fileExt, "", -1)
		}

		if fileBase == "" || fileExt == "" {
			p.logger.Debug().
				Str("file", fileName).
				Msg("invalid file name format, ignoring")
			continue
		}

		if fileExt == ".conf" || fileExt == ".json" {
			p.logger.Debug().
				Str("file", fileName).
				Msg("config file, ignoring")
			continue
		}

		if _, reserved := p.reservedNames[fileBase]; reserved {
			p.logger.Warn().
				Str("file", fileName).
				Msg("reserved plugin name, ignoring")
			continue
		}

		var cmdName string

		switch mode := fi.Mode(); {
		case mode.IsRegular():
			cmdName = filepath.Join(p.pluginDir, fi.Name())
		case mode&os.ModeSymlink != 0:
			resolvedSymlink, err := filepath.EvalSymlinks(filepath.Join(p.pluginDir, fi.Name()))
			if err != nil {
				p.logger.Warn().
					Err(err).
					Str("file", fi.Name()).
					Msg("Error resolving symlink, ignoring")
				continue
			}
			cmdName = resolvedSymlink
		default:
			p.logger.Debug().
				Str("file", fileName).
				Msg("not a regular file or symlink, ignoring")
			continue // just ignore it
		}

		if runtime.GOOS != "windows" {
			// windows doesn't have an e'x'ecutable bit, all files are
			// 'potentially' executable - binary exe, interpreted scripts, etc.
			if perm := fi.Mode().Perm() & 0111; perm != 73 {
				p.logger.Warn().
					Str("file", cmdName).
					Str("perms", fmt.Sprintf("%q", fi.Mode().Perm())).
					Msg("executable bit not set, ignoring")
				continue
			}
		}

		if b != nil && b.IsBuiltin(fileBase) {
			p.logger.Warn().Str("id", fileBase).Msg("Builtin collector already enabled, skipping plugin")
			continue
		}

		var cfg map[string][]string

		// check for config file
		cfgFile := filepath.Join(p.pluginDir, fmt.Sprintf("%s.json", fileBase))
		if data, err := ioutil.ReadFile(cfgFile); err != nil {
			if !os.IsNotExist(err) {
				p.logger.Warn().
					Err(err).
					Str("config", cfgFile).
					Str("plugin", fileBase).Msg("plugin config")
			}
		} else {
			if len(data) > 0 {
				err := json.Unmarshal(data, &cfg)
				if err != nil {
					p.logger.Warn().
						Err(err).
						Str("config", cfgFile).
						Str("plugin", fileBase).
						Str("data", string(data)).
						Msg("parsing config")
				}

				p.logger.Debug().
					Str("config", fmt.Sprintf("%+v", cfg)).
					Msg("loaded plugin config")
			}
		}

		// parse fileBase for _ttl(.+)
		matches := ttlRx.FindAllStringSubmatch(fileBase, -1)
		var runTTL time.Duration
		if len(matches) > 0 && len(matches[0]) > 1 {
			ttl := matches[0][1]
			if ttl != "" {
				if !ttlUnitRx.MatchString(ttl) {
					ttl += viper.GetString(config.KeyPluginTTLUnits)
				}

				if d, err := time.ParseDuration(ttl); err != nil {
					p.logger.Warn().Err(err).Str("ttl", ttl).Msg("parsing plugin ttl, ignoring ttl")
				} else {
					runTTL = d
				}
			}
		}

		if cfg == nil {
			plug, ok := p.active[fileBase]
			if !ok {
				p.active[fileBase] = &plugin{
					ctx:    p.ctx,
					id:     fileBase,
					name:   fileBase,
					logger: p.logger.With().Str("plugin", fileBase).Logger(),
					runDir: p.pluginDir,
					runTTL: runTTL,
				}
				plug = p.active[fileBase]
			}

			appstats.MapIncrementInt("plugins", "total")
			plug.command = cmdName
			p.logger.Info().
				Str("id", fileBase).
				Str("cmd", cmdName).
				Msg("Activating plugin")

		} else {
			for inst, args := range cfg {
				pluginName := fmt.Sprintf("%s`%s", fileBase, inst)
				plug, ok := p.active[pluginName]
				if !ok {
					p.active[pluginName] = &plugin{
						ctx:          p.ctx,
						id:           fileBase,
						instanceID:   inst,
						instanceArgs: args,
						name:         pluginName,
						logger:       p.logger.With().Str("plugin", pluginName).Logger(),
						runDir:       p.pluginDir,
						runTTL:       runTTL,
					}
					plug = p.active[pluginName]
				}

				appstats.MapIncrementInt("plugins", "total")
				plug.command = cmdName
				p.logger.Info().
					Str("id", pluginName).
					Str("cmd", cmdName).
					Msg("Activating plugin")

			}
		}
	}

	if len(p.active) == 0 {
		return errors.New("No active plugins found")
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Hajime Hoshi
//
// 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.

// +build android ios

package ui

import (
	"errors"
	"image"
	"runtime"
	"sync"
	"time"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/input"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

var (
	glContextCh = make(chan gl.Context)
	renderCh    = make(chan struct{})
	renderChEnd = make(chan struct{})
	currentUI   = &userInterface{}
)

func Render(chError <-chan error) error {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	if chError == nil {
		return errors.New("ui: chError must not be nil")
	}
	// TODO: Check this is called on the rendering thread
	select {
	case renderCh <- struct{}{}:
		return opengl.GetContext().DoWork(chError, renderChEnd)
	case <-time.After(500 * time.Millisecond):
		// This function must not be blocked. We need to break for timeout.
		return nil
	}
}

type userInterface struct {
	width       int
	height      int
	scale       float64
	sizeChanged bool

	// Used for gomobile-build
	fullscreenScale    float64
	fullscreenWidthPx  int
	fullscreenHeightPx int

	m sync.RWMutex
}

var (
	deviceScaleVal float64
	deviceScaleM   sync.Mutex
)

func deviceScale() float64 {
	deviceScaleM.Lock()
	defer deviceScaleM.Unlock()

	if deviceScaleVal == 0 {
		deviceScaleVal = devicescale.Get()
	}
	return deviceScaleVal
}

// appMain is the main routine for gomobile-build mode.
func appMain(a app.App) {
	var glctx gl.Context
	touches := map[touch.Sequence]*input.Touch{}
	for e := range a.Events() {
		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				glctx = nil
			}
		case size.Event:
			setFullscreen(e.WidthPx, e.HeightPx)
		case paint.Event:
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderChEnd
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				t := input.NewTouch(int(e.Sequence), int(x), int(y))
				touches[e.Sequence] = t
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			ts := []*input.Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			UpdateTouches(ts)
		}
	}
}

func Run(width, height int, scale float64, title string, g GraphicsContext, mainloop bool) error {
	u := currentUI

	u.m.Lock()
	u.width = width
	u.height = height
	u.scale = scale
	u.sizeChanged = true
	u.m.Unlock()
	// title is ignored?

	if mainloop {
		ctx := <-glContextCh
		opengl.InitWithContext(ctx)
	} else {
		opengl.Init()
	}

	// Force to set the screen size
	u.updateGraphicsContext(g)
	for {
		if err := u.update(g); err != nil {
			return err
		}
	}
}

// RunMainThreadLoop runs the main routine for gomobile-build.
func RunMainThreadLoop(ch <-chan error) error {
	go func() {
		// As mobile apps never ends, RunMainThreadLoop can't return.
		// Just panic here.
		err := <-ch
		panic(err)
	}()
	app.Main(appMain)
	return nil
}

func (u *userInterface) updateGraphicsContext(g GraphicsContext) {
	width, height := 0, 0
	actualScale := 0.0

	u.m.Lock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		width = u.width
		height = u.height
		actualScale = u.scaleImpl() * deviceScale()
	}
	u.sizeChanged = false
	u.m.Unlock()

	if sizeChanged {
		// Sizing also calls GL functions
		g.SetSize(width, height, actualScale)
	}
}

func actualScale() float64 {
	return currentUI.actualScale()
}

func (u *userInterface) actualScale() float64 {
	u.m.Lock()
	s := u.scaleImpl() * deviceScale()
	u.m.Unlock()
	return s
}

func (u *userInterface) scaleImpl() float64 {
	scale := u.scale
	if u.fullscreenScale != 0 {
		scale = u.fullscreenScale
	}
	return scale
}

func (u *userInterface) update(g GraphicsContext) error {
render:
	for {
		select {
		case <-renderCh:
			break render
		case <-time.After(500 * time.Millisecond):
			hooks.SuspendAudio()
			continue
		}
	}
	hooks.ResumeAudio()

	defer func() {
		renderChEnd <- struct{}{}
	}()

	if err := g.Update(func() {
		u.updateGraphicsContext(g)
	}); err != nil {
		return err
	}
	return nil
}

func screenSize() (int, int) {
	return currentUI.screenSize()
}

func (u *userInterface) screenSize() (int, int) {
	u.m.Lock()
	w, h := u.width, u.height
	u.m.Unlock()
	return w, h
}

func MonitorSize() (int, int) {
	// TODO: This function should return fullscreenWidthPx, fullscreenHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

func SetScreenSize(width, height int) bool {
	currentUI.setScreenSize(width, height)
	return true
}

func (u *userInterface) setScreenSize(width, height int) {
	u.m.Lock()
	if u.width != width || u.height != height {
		u.width = width
		u.height = height
		u.updateFullscreenScaleIfNeeded()
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func SetScreenScale(scale float64) bool {
	currentUI.setScreenScale(scale)
	return false
}

func (u *userInterface) setScreenScale(scale float64) {
	u.m.Lock()
	if u.scale != scale {
		u.scale = scale
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func ScreenScale() float64 {
	u := currentUI
	u.m.RLock()
	s := u.scale
	u.m.RUnlock()
	return s
}

func setFullscreen(widthPx, heightPx int) {
	currentUI.setFullscreen(widthPx, heightPx)
}

func (u *userInterface) setFullscreen(widthPx, heightPx int) {
	u.m.Lock()
	u.fullscreenWidthPx = widthPx
	u.fullscreenHeightPx = heightPx
	u.updateFullscreenScaleIfNeeded()
	u.sizeChanged = true
	u.m.Unlock()
}

func (u *userInterface) updateFullscreenScaleIfNeeded() {
	if u.fullscreenWidthPx == 0 || u.fullscreenHeightPx == 0 {
		return
	}
	w, h := u.width, u.height
	scaleX := float64(u.fullscreenWidthPx) / float64(w)
	scaleY := float64(u.fullscreenHeightPx) / float64(h)
	scale := scaleX
	if scale > scaleY {
		scale = scaleY
	}
	u.fullscreenScale = scale / deviceScale()
	u.sizeChanged = true
}

func ScreenPadding() (x0, y0, x1, y1 float64) {
	return currentUI.screenPadding()
}

func (u *userInterface) screenPadding() (x0, y0, x1, y1 float64) {
	u.m.Lock()
	x0, y0, x1, y1 = u.screenPaddingImpl()
	u.m.Unlock()
	return
}

func (u *userInterface) screenPaddingImpl() (x0, y0, x1, y1 float64) {
	if u.fullscreenScale == 0 {
		return 0, 0, 0, 0
	}
	s := u.fullscreenScale * deviceScale()
	ox := (float64(u.fullscreenWidthPx) - float64(u.width)*s) / 2
	oy := (float64(u.fullscreenHeightPx) - float64(u.height)*s) / 2
	return ox, oy, ox, oy
}

func AdjustedCursorPosition() (x, y int) {
	return currentUI.adjustPosition(input.Get().CursorPosition())
}

func AdjustedTouches() []*input.Touch {
	ts := input.Get().Touches()
	adjusted := make([]*input.Touch, len(ts))
	for i, t := range ts {
		x, y := currentUI.adjustPosition(t.Position())
		adjusted[i] = input.NewTouch(t.ID(), x, y)
	}
	return adjusted
}

func (u *userInterface) adjustPosition(x, y int) (int, int) {
	u.m.Lock()
	ox, oy, _, _ := u.screenPaddingImpl()
	s := u.scaleImpl()
	as := s * deviceScale()
	u.m.Unlock()
	return int(float64(x)/s - ox/as), int(float64(y)/s - oy/as)
}

func IsCursorVisible() bool {
	return false
}

func SetCursorVisible(visible bool) {
	// Do nothing
}

func IsFullscreen() bool {
	return false
}

func SetFullscreen(fullscreen bool) {
	// Do nothing
}

func IsRunnableInBackground() bool {
	return false
}

func SetRunnableInBackground(runnableInBackground bool) {
	// Do nothing
}

func SetWindowTitle(title string) {
	// Do nothing
}

func SetWindowIcon(iconImages []image.Image) {
	// Do nothing
}

func IsWindowDecorated() bool {
	return false
}

func SetWindowDecorated(decorated bool) {
	// Do nothing
}

func IsVsyncEnabled() bool {
	return true
}

func SetVsyncEnabled(enabled bool) {
	// Do nothing
}

func UpdateTouches(touches []*input.Touch) {
	input.Get().UpdateTouches(touches)
}
<commit_msg>ui: Bug fix: compile error on mobiles<commit_after>// Copyright 2016 Hajime Hoshi
//
// 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.

// +build android ios

package ui

import (
	"errors"
	"image"
	"runtime"
	"sync"
	"time"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/input"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

var (
	glContextCh = make(chan gl.Context)
	renderCh    = make(chan struct{})
	renderChEnd = make(chan struct{})
	currentUI   = &userInterface{}
)

func Render(chError <-chan error) error {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	if chError == nil {
		return errors.New("ui: chError must not be nil")
	}
	// TODO: Check this is called on the rendering thread
	select {
	case renderCh <- struct{}{}:
		return opengl.GetContext().DoWork(chError, renderChEnd)
	case <-time.After(500 * time.Millisecond):
		// This function must not be blocked. We need to break for timeout.
		return nil
	}
}

type userInterface struct {
	width       int
	height      int
	scale       float64
	sizeChanged bool

	// Used for gomobile-build
	fullscreenScale    float64
	fullscreenWidthPx  int
	fullscreenHeightPx int

	m sync.RWMutex
}

var (
	deviceScaleVal float64
	deviceScaleM   sync.Mutex
)

func getDeviceScale() float64 {
	deviceScaleM.Lock()
	defer deviceScaleM.Unlock()

	if deviceScaleVal == 0 {
		deviceScaleVal = devicescale.Get()
	}
	return deviceScaleVal
}

// appMain is the main routine for gomobile-build mode.
func appMain(a app.App) {
	var glctx gl.Context
	touches := map[touch.Sequence]*input.Touch{}
	for e := range a.Events() {
		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				glctx = nil
			}
		case size.Event:
			setFullscreen(e.WidthPx, e.HeightPx)
		case paint.Event:
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderChEnd
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := getDeviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				t := input.NewTouch(int(e.Sequence), int(x), int(y))
				touches[e.Sequence] = t
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			ts := []*input.Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			UpdateTouches(ts)
		}
	}
}

func Run(width, height int, scale float64, title string, g GraphicsContext, mainloop bool) error {
	u := currentUI

	u.m.Lock()
	u.width = width
	u.height = height
	u.scale = scale
	u.sizeChanged = true
	u.m.Unlock()
	// title is ignored?

	if mainloop {
		ctx := <-glContextCh
		opengl.InitWithContext(ctx)
	} else {
		opengl.Init()
	}

	// Force to set the screen size
	u.updateGraphicsContext(g)
	for {
		if err := u.update(g); err != nil {
			return err
		}
	}
}

// RunMainThreadLoop runs the main routine for gomobile-build.
func RunMainThreadLoop(ch <-chan error) error {
	go func() {
		// As mobile apps never ends, RunMainThreadLoop can't return.
		// Just panic here.
		err := <-ch
		panic(err)
	}()
	app.Main(appMain)
	return nil
}

func (u *userInterface) updateGraphicsContext(g GraphicsContext) {
	width, height := 0, 0
	actualScale := 0.0

	u.m.Lock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		width = u.width
		height = u.height
		actualScale = u.scaleImpl() * getDeviceScale()
	}
	u.sizeChanged = false
	u.m.Unlock()

	if sizeChanged {
		// Sizing also calls GL functions
		g.SetSize(width, height, actualScale)
	}
}

func actualScale() float64 {
	return currentUI.actualScale()
}

func (u *userInterface) actualScale() float64 {
	u.m.Lock()
	s := u.scaleImpl() * getDeviceScale()
	u.m.Unlock()
	return s
}

func (u *userInterface) scaleImpl() float64 {
	scale := u.scale
	if u.fullscreenScale != 0 {
		scale = u.fullscreenScale
	}
	return scale
}

func (u *userInterface) update(g GraphicsContext) error {
render:
	for {
		select {
		case <-renderCh:
			break render
		case <-time.After(500 * time.Millisecond):
			hooks.SuspendAudio()
			continue
		}
	}
	hooks.ResumeAudio()

	defer func() {
		renderChEnd <- struct{}{}
	}()

	if err := g.Update(func() {
		u.updateGraphicsContext(g)
	}); err != nil {
		return err
	}
	return nil
}

func screenSize() (int, int) {
	return currentUI.screenSize()
}

func (u *userInterface) screenSize() (int, int) {
	u.m.Lock()
	w, h := u.width, u.height
	u.m.Unlock()
	return w, h
}

func MonitorSize() (int, int) {
	// TODO: This function should return fullscreenWidthPx, fullscreenHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

func SetScreenSize(width, height int) bool {
	currentUI.setScreenSize(width, height)
	return true
}

func (u *userInterface) setScreenSize(width, height int) {
	u.m.Lock()
	if u.width != width || u.height != height {
		u.width = width
		u.height = height
		u.updateFullscreenScaleIfNeeded()
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func SetScreenScale(scale float64) bool {
	currentUI.setScreenScale(scale)
	return false
}

func (u *userInterface) setScreenScale(scale float64) {
	u.m.Lock()
	if u.scale != scale {
		u.scale = scale
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func ScreenScale() float64 {
	u := currentUI
	u.m.RLock()
	s := u.scale
	u.m.RUnlock()
	return s
}

func setFullscreen(widthPx, heightPx int) {
	currentUI.setFullscreen(widthPx, heightPx)
}

func (u *userInterface) setFullscreen(widthPx, heightPx int) {
	u.m.Lock()
	u.fullscreenWidthPx = widthPx
	u.fullscreenHeightPx = heightPx
	u.updateFullscreenScaleIfNeeded()
	u.sizeChanged = true
	u.m.Unlock()
}

func (u *userInterface) updateFullscreenScaleIfNeeded() {
	if u.fullscreenWidthPx == 0 || u.fullscreenHeightPx == 0 {
		return
	}
	w, h := u.width, u.height
	scaleX := float64(u.fullscreenWidthPx) / float64(w)
	scaleY := float64(u.fullscreenHeightPx) / float64(h)
	scale := scaleX
	if scale > scaleY {
		scale = scaleY
	}
	u.fullscreenScale = scale / getDeviceScale()
	u.sizeChanged = true
}

func ScreenPadding() (x0, y0, x1, y1 float64) {
	return currentUI.screenPadding()
}

func (u *userInterface) screenPadding() (x0, y0, x1, y1 float64) {
	u.m.Lock()
	x0, y0, x1, y1 = u.screenPaddingImpl()
	u.m.Unlock()
	return
}

func (u *userInterface) screenPaddingImpl() (x0, y0, x1, y1 float64) {
	if u.fullscreenScale == 0 {
		return 0, 0, 0, 0
	}
	s := u.fullscreenScale * getDeviceScale()
	ox := (float64(u.fullscreenWidthPx) - float64(u.width)*s) / 2
	oy := (float64(u.fullscreenHeightPx) - float64(u.height)*s) / 2
	return ox, oy, ox, oy
}

func AdjustedCursorPosition() (x, y int) {
	return currentUI.adjustPosition(input.Get().CursorPosition())
}

func AdjustedTouches() []*input.Touch {
	ts := input.Get().Touches()
	adjusted := make([]*input.Touch, len(ts))
	for i, t := range ts {
		x, y := currentUI.adjustPosition(t.Position())
		adjusted[i] = input.NewTouch(t.ID(), x, y)
	}
	return adjusted
}

func (u *userInterface) adjustPosition(x, y int) (int, int) {
	u.m.Lock()
	ox, oy, _, _ := u.screenPaddingImpl()
	s := u.scaleImpl()
	as := s * getDeviceScale()
	u.m.Unlock()
	return int(float64(x)/s - ox/as), int(float64(y)/s - oy/as)
}

func IsCursorVisible() bool {
	return false
}

func SetCursorVisible(visible bool) {
	// Do nothing
}

func IsFullscreen() bool {
	return false
}

func SetFullscreen(fullscreen bool) {
	// Do nothing
}

func IsRunnableInBackground() bool {
	return false
}

func SetRunnableInBackground(runnableInBackground bool) {
	// Do nothing
}

func SetWindowTitle(title string) {
	// Do nothing
}

func SetWindowIcon(iconImages []image.Image) {
	// Do nothing
}

func IsWindowDecorated() bool {
	return false
}

func SetWindowDecorated(decorated bool) {
	// Do nothing
}

func IsVsyncEnabled() bool {
	return true
}

func SetVsyncEnabled(enabled bool) {
	// Do nothing
}

func UpdateTouches(touches []*input.Touch) {
	input.Get().UpdateTouches(touches)
}
<|endoftext|>


<commit_before>package lib

import "math"

/*UpArrow ...*/
func UpArrow(base int64, exponant int64, upArrowAmount int64) int64 {
	if upArrowAmount <= 0 {
		return int64(base * exponant)
	} else if upArrowAmount == 1 {
		return int64(math.Pow(float64(base), float64(exponant)))
	} else if exponant == 1 {
		return int64(base)
	}
	return UpArrow(base, UpArrow(base, exponant-1, upArrowAmount), upArrowAmount-1)
}
<commit_msg>prevent negativ exponent<commit_after>package lib

import "math"

/*UpArrow ...*/
func UpArrow(base uint64, exponant uint64, upArrowAmount uint64) uint64 {
	if upArrowAmount <= 0 {
		return uint64(base * exponant)
	} else if upArrowAmount == 1 {
		return uint64(math.Pow(float64(base), float64(exponant)))
	} else if exponant <= 1 {
		return uint64(base)
	}
	return UpArrow(base, UpArrow(base, exponant-1, upArrowAmount), upArrowAmount-1)
}
<|endoftext|>