<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ecr"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
	"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func resourceAwsEcrRepository() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEcrRepositoryCreate,
		Read:   resourceAwsEcrRepositoryRead,
		Update: resourceAwsEcrRepositoryUpdate,
		Delete: resourceAwsEcrRepositoryDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Delete: schema.DefaultTimeout(20 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"encryption_configuration": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"encryption_type": {
							Type:     schema.TypeString,
							Optional: true,
							ValidateFunc: validation.StringInSlice([]string{
								ecr.EncryptionTypeAes256,
								ecr.EncryptionTypeKms,
							}, false),
							Default:  ecr.EncryptionTypeAes256,
							ForceNew: true,
						},
						"kms_key": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
							ForceNew: true,
						},
					},
				},
				DiffSuppressFunc: suppressMissingOptionalConfigurationBlock,
				ForceNew:         true,
			},
			"image_scanning_configuration": {
				Type:     schema.TypeList,
				MaxItems: 1,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"scan_on_push": {
							Type:     schema.TypeBool,
							Required: true,
						},
					},
				},
				DiffSuppressFunc: suppressMissingOptionalConfigurationBlock,
			},
			"image_tag_mutability": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  ecr.ImageTagMutabilityMutable,
				ValidateFunc: validation.StringInSlice([]string{
					ecr.ImageTagMutabilityMutable,
					ecr.ImageTagMutabilityImmutable,
				}, false),
			},
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"registry_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"repository_url": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

func resourceAwsEcrRepositoryCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecrconn

	input := ecr.CreateRepositoryInput{
		ImageTagMutability:      aws.String(d.Get("image_tag_mutability").(string)),
		RepositoryName:          aws.String(d.Get("name").(string)),
		Tags:                    keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().EcrTags(),
		EncryptionConfiguration: expandEcrRepositoryEncryptionConfiguration(d.Get("encryption_configuration").([]interface{})),
	}

	imageScanningConfigs := d.Get("image_scanning_configuration").([]interface{})
	if len(imageScanningConfigs) > 0 {
		imageScanningConfig := imageScanningConfigs[0]
		if imageScanningConfig != nil {
			configMap := imageScanningConfig.(map[string]interface{})
			input.ImageScanningConfiguration = &ecr.ImageScanningConfiguration{
				ScanOnPush: aws.Bool(configMap["scan_on_push"].(bool)),
			}
		}
	}

	log.Printf("[DEBUG] Creating ECR repository: %#v", input)
	out, err := conn.CreateRepository(&input)
	if err != nil {
		return fmt.Errorf("error creating ECR repository: %s", err)
	}

	repository := *out.Repository

	log.Printf("[DEBUG] ECR repository created: %q", *repository.RepositoryArn)

	d.SetId(aws.StringValue(repository.RepositoryName))

	return resourceAwsEcrRepositoryRead(d, meta)
}

func resourceAwsEcrRepositoryRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecrconn
	ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

	log.Printf("[DEBUG] Reading ECR repository %s", d.Id())
	var out *ecr.DescribeRepositoriesOutput
	input := &ecr.DescribeRepositoriesInput{
		RepositoryNames: aws.StringSlice([]string{d.Id()}),
	}

	var err error
	err = resource.Retry(1*time.Minute, func() *resource.RetryError {
		out, err = conn.DescribeRepositories(input)
		if d.IsNewResource() && isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
			return resource.RetryableError(err)
		}
		if err != nil {
			return resource.NonRetryableError(err)
		}
		return nil
	})

	if isResourceTimeoutError(err) {
		out, err = conn.DescribeRepositories(input)
	}

	if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
		log.Printf("[WARN] ECR Repository (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	if err != nil {
		return fmt.Errorf("error reading ECR repository: %s", err)
	}

	repository := out.Repositories[0]
	arn := aws.StringValue(repository.RepositoryArn)

	d.Set("arn", arn)
	d.Set("name", repository.RepositoryName)
	d.Set("registry_id", repository.RegistryId)
	d.Set("repository_url", repository.RepositoryUri)
	d.Set("image_tag_mutability", repository.ImageTagMutability)

	tags, err := keyvaluetags.EcrListTags(conn, arn)
	if err != nil {
		return fmt.Errorf("error listing tags for ECR Repository (%s): %w", arn, err)
	}
	if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
		return fmt.Errorf("error setting tags for ECR Repository (%s): %w", arn, err)
	}

	if err := d.Set("image_scanning_configuration", flattenImageScanningConfiguration(repository.ImageScanningConfiguration)); err != nil {
		return fmt.Errorf("error setting image_scanning_configuration for ECR Repository (%s): %w", arn, err)
	}

	if err := d.Set("encryption_configuration", flattenEcrRepositoryEncryptionConfiguration(repository.EncryptionConfiguration)); err != nil {
		return fmt.Errorf("error setting encryption_configuration for ECR Repository (%s): %w", arn, err)
	}

	return nil
}

func flattenImageScanningConfiguration(isc *ecr.ImageScanningConfiguration) []map[string]interface{} {
	if isc == nil {
		return nil
	}

	config := make(map[string]interface{})
	config["scan_on_push"] = aws.BoolValue(isc.ScanOnPush)

	return []map[string]interface{}{
		config,
	}
}

func expandEcrRepositoryEncryptionConfiguration(data []interface{}) *ecr.EncryptionConfiguration {
	if len(data) == 0 || data[0] == nil {
		return nil
	}

	ec := data[0].(map[string]interface{})
	config := &ecr.EncryptionConfiguration{
		EncryptionType: aws.String(ec["encryption_type"].(string)),
	}
	if v, ok := ec["kms_key"]; ok {
		if s := v.(string); s != "" {
			config.KmsKey = aws.String(v.(string))
		}
	}
	return config
}

func flattenEcrRepositoryEncryptionConfiguration(ec *ecr.EncryptionConfiguration) []map[string]interface{} {
	config := map[string]interface{}{
		"encryption_type": aws.StringValue(ec.EncryptionType),
		"kms_key":         aws.StringValue(ec.KmsKey),
	}

	return []map[string]interface{}{
		config,
	}
}

func resourceAwsEcrRepositoryUpdate(d *schema.ResourceData, meta interface{}) error {
	arn := d.Get("arn").(string)
	conn := meta.(*AWSClient).ecrconn

	if d.HasChange("image_tag_mutability") {
		if err := resourceAwsEcrRepositoryUpdateImageTagMutability(conn, d); err != nil {
			return err
		}
	}

	if d.HasChange("image_scanning_configuration") {
		if err := resourceAwsEcrRepositoryUpdateImageScanningConfiguration(conn, d); err != nil {
			return err
		}
	}

	if d.HasChange("encryption_configuration") {
		if err := resourceAwsEcrRepositoryUpdateImageScanningConfiguration(conn, d); err != nil {
			return err
		}
	}

	if d.HasChange("tags") {
		o, n := d.GetChange("tags")

		if err := keyvaluetags.EcrUpdateTags(conn, arn, o, n); err != nil {
			return fmt.Errorf("error updating ECR Repository (%s) tags: %s", arn, err)
		}
	}

	return resourceAwsEcrRepositoryRead(d, meta)
}

func resourceAwsEcrRepositoryDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecrconn

	_, err := conn.DeleteRepository(&ecr.DeleteRepositoryInput{
		RepositoryName: aws.String(d.Id()),
		RegistryId:     aws.String(d.Get("registry_id").(string)),
		Force:          aws.Bool(true),
	})
	if err != nil {
		if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
			return nil
		}
		return fmt.Errorf("error deleting ECR repository: %s", err)
	}

	log.Printf("[DEBUG] Waiting for ECR Repository %q to be deleted", d.Id())
	input := &ecr.DescribeRepositoriesInput{
		RepositoryNames: aws.StringSlice([]string{d.Id()}),
	}
	err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError {
		_, err = conn.DescribeRepositories(input)
		if err != nil {
			if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
				return nil
			}
			return resource.NonRetryableError(err)
		}

		return resource.RetryableError(fmt.Errorf("%q: Timeout while waiting for the ECR Repository to be deleted", d.Id()))
	})
	if isResourceTimeoutError(err) {
		_, err = conn.DescribeRepositories(input)
	}

	if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
		return nil
	}

	if err != nil {
		return fmt.Errorf("error deleting ECR repository: %s", err)
	}

	log.Printf("[DEBUG] repository %q deleted.", d.Get("name").(string))

	return nil
}

func resourceAwsEcrRepositoryUpdateImageTagMutability(conn *ecr.ECR, d *schema.ResourceData) error {
	input := &ecr.PutImageTagMutabilityInput{
		ImageTagMutability: aws.String(d.Get("image_tag_mutability").(string)),
		RepositoryName:     aws.String(d.Id()),
		RegistryId:         aws.String(d.Get("registry_id").(string)),
	}

	_, err := conn.PutImageTagMutability(input)
	if err != nil {
		return fmt.Errorf("Error setting image tag mutability: %s", err.Error())
	}

	return nil
}
func resourceAwsEcrRepositoryUpdateImageScanningConfiguration(conn *ecr.ECR, d *schema.ResourceData) error {

	var ecrImageScanningConfig ecr.ImageScanningConfiguration
	imageScanningConfigs := d.Get("image_scanning_configuration").([]interface{})
	if len(imageScanningConfigs) > 0 {
		imageScanningConfig := imageScanningConfigs[0]
		if imageScanningConfig != nil {
			configMap := imageScanningConfig.(map[string]interface{})
			ecrImageScanningConfig.ScanOnPush = aws.Bool(configMap["scan_on_push"].(bool))
		}
	}

	input := &ecr.PutImageScanningConfigurationInput{
		ImageScanningConfiguration: &ecrImageScanningConfig,
		RepositoryName:             aws.String(d.Id()),
		RegistryId:                 aws.String(d.Get("registry_id").(string)),
	}

	_, err := conn.PutImageScanningConfiguration(input)
	if err != nil {
		return fmt.Errorf("Error setting image scanning configuration: %s", err.Error())
	}

	return nil
}
<commit_msg>Update aws/resource_aws_ecr_repository.go<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ecr"
	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
	"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func resourceAwsEcrRepository() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEcrRepositoryCreate,
		Read:   resourceAwsEcrRepositoryRead,
		Update: resourceAwsEcrRepositoryUpdate,
		Delete: resourceAwsEcrRepositoryDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Timeouts: &schema.ResourceTimeout{
			Delete: schema.DefaultTimeout(20 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"arn": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"encryption_configuration": {
				Type:     schema.TypeList,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"encryption_type": {
							Type:     schema.TypeString,
							Optional: true,
							ValidateFunc: validation.StringInSlice([]string{
								ecr.EncryptionTypeAes256,
								ecr.EncryptionTypeKms,
							}, false),
							Default:  ecr.EncryptionTypeAes256,
							ForceNew: true,
						},
						"kms_key": {
							Type:     schema.TypeString,
							Optional: true,
							Computed: true,
							ForceNew: true,
						},
					},
				},
				DiffSuppressFunc: suppressMissingOptionalConfigurationBlock,
				ForceNew:         true,
			},
			"image_scanning_configuration": {
				Type:     schema.TypeList,
				MaxItems: 1,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"scan_on_push": {
							Type:     schema.TypeBool,
							Required: true,
						},
					},
				},
				DiffSuppressFunc: suppressMissingOptionalConfigurationBlock,
			},
			"image_tag_mutability": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  ecr.ImageTagMutabilityMutable,
				ValidateFunc: validation.StringInSlice([]string{
					ecr.ImageTagMutabilityMutable,
					ecr.ImageTagMutabilityImmutable,
				}, false),
			},
			"name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"registry_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"repository_url": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"tags": tagsSchema(),
		},
	}
}

func resourceAwsEcrRepositoryCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecrconn

	input := ecr.CreateRepositoryInput{
		ImageTagMutability:      aws.String(d.Get("image_tag_mutability").(string)),
		RepositoryName:          aws.String(d.Get("name").(string)),
		Tags:                    keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().EcrTags(),
		EncryptionConfiguration: expandEcrRepositoryEncryptionConfiguration(d.Get("encryption_configuration").([]interface{})),
	}

	imageScanningConfigs := d.Get("image_scanning_configuration").([]interface{})
	if len(imageScanningConfigs) > 0 {
		imageScanningConfig := imageScanningConfigs[0]
		if imageScanningConfig != nil {
			configMap := imageScanningConfig.(map[string]interface{})
			input.ImageScanningConfiguration = &ecr.ImageScanningConfiguration{
				ScanOnPush: aws.Bool(configMap["scan_on_push"].(bool)),
			}
		}
	}

	log.Printf("[DEBUG] Creating ECR repository: %#v", input)
	out, err := conn.CreateRepository(&input)
	if err != nil {
		return fmt.Errorf("error creating ECR repository: %s", err)
	}

	repository := *out.Repository

	log.Printf("[DEBUG] ECR repository created: %q", *repository.RepositoryArn)

	d.SetId(aws.StringValue(repository.RepositoryName))

	return resourceAwsEcrRepositoryRead(d, meta)
}

func resourceAwsEcrRepositoryRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecrconn
	ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

	log.Printf("[DEBUG] Reading ECR repository %s", d.Id())
	var out *ecr.DescribeRepositoriesOutput
	input := &ecr.DescribeRepositoriesInput{
		RepositoryNames: aws.StringSlice([]string{d.Id()}),
	}

	var err error
	err = resource.Retry(1*time.Minute, func() *resource.RetryError {
		out, err = conn.DescribeRepositories(input)
		if d.IsNewResource() && isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
			return resource.RetryableError(err)
		}
		if err != nil {
			return resource.NonRetryableError(err)
		}
		return nil
	})

	if isResourceTimeoutError(err) {
		out, err = conn.DescribeRepositories(input)
	}

	if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
		log.Printf("[WARN] ECR Repository (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	if err != nil {
		return fmt.Errorf("error reading ECR repository: %s", err)
	}

	repository := out.Repositories[0]
	arn := aws.StringValue(repository.RepositoryArn)

	d.Set("arn", arn)
	d.Set("name", repository.RepositoryName)
	d.Set("registry_id", repository.RegistryId)
	d.Set("repository_url", repository.RepositoryUri)
	d.Set("image_tag_mutability", repository.ImageTagMutability)

	tags, err := keyvaluetags.EcrListTags(conn, arn)
	if err != nil {
		return fmt.Errorf("error listing tags for ECR Repository (%s): %w", arn, err)
	}
	if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
		return fmt.Errorf("error setting tags for ECR Repository (%s): %w", arn, err)
	}

	if err := d.Set("image_scanning_configuration", flattenImageScanningConfiguration(repository.ImageScanningConfiguration)); err != nil {
		return fmt.Errorf("error setting image_scanning_configuration for ECR Repository (%s): %w", arn, err)
	}

	if err := d.Set("encryption_configuration", flattenEcrRepositoryEncryptionConfiguration(repository.EncryptionConfiguration)); err != nil {
		return fmt.Errorf("error setting encryption_configuration for ECR Repository (%s): %w", arn, err)
	}

	return nil
}

func flattenImageScanningConfiguration(isc *ecr.ImageScanningConfiguration) []map[string]interface{} {
	if isc == nil {
		return nil
	}

	config := make(map[string]interface{})
	config["scan_on_push"] = aws.BoolValue(isc.ScanOnPush)

	return []map[string]interface{}{
		config,
	}
}

func expandEcrRepositoryEncryptionConfiguration(data []interface{}) *ecr.EncryptionConfiguration {
	if len(data) == 0 || data[0] == nil {
		return nil
	}

	ec := data[0].(map[string]interface{})
	config := &ecr.EncryptionConfiguration{
		EncryptionType: aws.String(ec["encryption_type"].(string)),
	}
	if v, ok := ec["kms_key"]; ok {
		if s := v.(string); s != "" {
			config.KmsKey = aws.String(v.(string))
		}
	}
	return config
}

func flattenEcrRepositoryEncryptionConfiguration(ec *ecr.EncryptionConfiguration) []map[string]interface{} {
	config := map[string]interface{}{
		"encryption_type": aws.StringValue(ec.EncryptionType),
		"kms_key":         aws.StringValue(ec.KmsKey),
	}

	return []map[string]interface{}{
		config,
	}
}

func resourceAwsEcrRepositoryUpdate(d *schema.ResourceData, meta interface{}) error {
	arn := d.Get("arn").(string)
	conn := meta.(*AWSClient).ecrconn

	if d.HasChange("image_tag_mutability") {
		if err := resourceAwsEcrRepositoryUpdateImageTagMutability(conn, d); err != nil {
			return err
		}
	}

	if d.HasChange("image_scanning_configuration") {
		if err := resourceAwsEcrRepositoryUpdateImageScanningConfiguration(conn, d); err != nil {
			return err
		}
	}

	if d.HasChange("tags") {
		o, n := d.GetChange("tags")

		if err := keyvaluetags.EcrUpdateTags(conn, arn, o, n); err != nil {
			return fmt.Errorf("error updating ECR Repository (%s) tags: %s", arn, err)
		}
	}

	return resourceAwsEcrRepositoryRead(d, meta)
}

func resourceAwsEcrRepositoryDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecrconn

	_, err := conn.DeleteRepository(&ecr.DeleteRepositoryInput{
		RepositoryName: aws.String(d.Id()),
		RegistryId:     aws.String(d.Get("registry_id").(string)),
		Force:          aws.Bool(true),
	})
	if err != nil {
		if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
			return nil
		}
		return fmt.Errorf("error deleting ECR repository: %s", err)
	}

	log.Printf("[DEBUG] Waiting for ECR Repository %q to be deleted", d.Id())
	input := &ecr.DescribeRepositoriesInput{
		RepositoryNames: aws.StringSlice([]string{d.Id()}),
	}
	err = resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError {
		_, err = conn.DescribeRepositories(input)
		if err != nil {
			if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
				return nil
			}
			return resource.NonRetryableError(err)
		}

		return resource.RetryableError(fmt.Errorf("%q: Timeout while waiting for the ECR Repository to be deleted", d.Id()))
	})
	if isResourceTimeoutError(err) {
		_, err = conn.DescribeRepositories(input)
	}

	if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
		return nil
	}

	if err != nil {
		return fmt.Errorf("error deleting ECR repository: %s", err)
	}

	log.Printf("[DEBUG] repository %q deleted.", d.Get("name").(string))

	return nil
}

func resourceAwsEcrRepositoryUpdateImageTagMutability(conn *ecr.ECR, d *schema.ResourceData) error {
	input := &ecr.PutImageTagMutabilityInput{
		ImageTagMutability: aws.String(d.Get("image_tag_mutability").(string)),
		RepositoryName:     aws.String(d.Id()),
		RegistryId:         aws.String(d.Get("registry_id").(string)),
	}

	_, err := conn.PutImageTagMutability(input)
	if err != nil {
		return fmt.Errorf("Error setting image tag mutability: %s", err.Error())
	}

	return nil
}
func resourceAwsEcrRepositoryUpdateImageScanningConfiguration(conn *ecr.ECR, d *schema.ResourceData) error {

	var ecrImageScanningConfig ecr.ImageScanningConfiguration
	imageScanningConfigs := d.Get("image_scanning_configuration").([]interface{})
	if len(imageScanningConfigs) > 0 {
		imageScanningConfig := imageScanningConfigs[0]
		if imageScanningConfig != nil {
			configMap := imageScanningConfig.(map[string]interface{})
			ecrImageScanningConfig.ScanOnPush = aws.Bool(configMap["scan_on_push"].(bool))
		}
	}

	input := &ecr.PutImageScanningConfigurationInput{
		ImageScanningConfiguration: &ecrImageScanningConfig,
		RepositoryName:             aws.String(d.Id()),
		RegistryId:                 aws.String(d.Get("registry_id").(string)),
	}

	_, err := conn.PutImageScanningConfiguration(input)
	if err != nil {
		return fmt.Errorf("Error setting image scanning configuration: %s", err.Error())
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2012-2014 Miquel Sabaté Solà <mikisabate@gmail.com>
// This file is licensed under the MIT license.
// See the LICENSE file.

package user_agent

import "strings"

// Normalize the name of the operating system. By now, this just
// affects to Windows.
//
// Returns a string containing the normalized name for the Operating System.
func normalizeOS(name string) string {
	sp := strings.SplitN(name, " ", 3)
	if len(sp) != 3 {
		return name
	}

	switch sp[2] {
	case "5.0":
		return "Windows 2000"
	case "5.01":
		return "Windows 2000, Service Pack 1 (SP1)"
	case "5.1":
		return "Windows XP"
	case "5.2":
		return "Windows XP x64 Edition"
	case "6.0":
		return "Windows Vista"
	case "6.1":
		return "Windows 7"
	case "6.2":
		return "Windows 8"
	case "6.3":
		return "Windows 8.1"
	}
	return name
}

// Guess the OS, the localization and if this is a mobile device for a
// Webkit-powered browser.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func webkit(p *UserAgent, comment []string) {
	if p.platform == "webOS" {
		p.browser.Name = p.platform
		p.os = "Palm"
		if len(comment) > 2 {
			p.localization = comment[2]
		}
		p.mobile = true
	} else if p.platform == "Symbian" {
		p.mobile = true
		p.browser.Name = p.platform
		p.os = comment[0]
	} else if p.platform == "Linux" {
		p.mobile = true
		if p.browser.Name == "Safari" {
			p.browser.Name = "Android"
		}
		if len(comment) > 1 {
			if comment[1] == "U" {
				if len(comment) > 2 {
					p.os = comment[2]
				} else {
					p.mobile = false
					p.os = comment[0]
				}
			} else {
				p.os = comment[1]
			}
		}
		if len(comment) > 3 {
			p.localization = comment[3]
		}
	} else if len(comment) > 0 {
		if len(comment) > 3 {
			p.localization = comment[3]
		}
		if strings.HasPrefix(comment[0], "Windows NT") {
			p.os = normalizeOS(comment[0])
		} else if len(comment) < 2 {
			p.localization = comment[0]
		} else if len(comment) < 3 {
			if !p.googleBot() {
				p.os = normalizeOS(comment[1])
			}
		} else {
			p.os = normalizeOS(comment[2])
		}
		if p.platform == "BlackBerry" {
			p.browser.Name = p.platform
			if p.os == "Touch" {
				p.os = p.platform
			}
		}
	}
}

// Guess the OS, the localization and if this is a mobile device
// for a Gecko-powered browser.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func gecko(p *UserAgent, comment []string) {
	if len(comment) > 1 {
		if comment[1] == "U" {
			if len(comment) > 2 {
				p.os = normalizeOS(comment[2])
			} else {
				p.os = normalizeOS(comment[1])
			}
		} else {
			if p.platform == "Android" {
				p.mobile = true
				p.platform, p.os = normalizeOS(comment[1]), p.platform
			} else if comment[0] == "Mobile" || comment[0] == "Tablet" {
				p.mobile = true
				p.os = "FirefoxOS"
			} else {
				if p.os == "" {
					p.os = normalizeOS(comment[1])
				}
			}
		}
		if len(comment) > 3 {
			p.localization = comment[3]
		}
	}
}

// Guess the OS, the localization and if this is a mobile device
// for Internet Explorer.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func trident(p *UserAgent, comment []string) {
	// Internet Explorer only runs on Windows.
	p.platform = "Windows"

	// The OS can be set before to handle a new case in IE11.
	if p.os == "" {
		if len(comment) > 2 {
			p.os = normalizeOS(comment[2])
		} else {
			p.os = "Windows NT 4.0"
		}
	}

	// Last but not least, let's detect if it comes from a mobile device.
	for _, v := range comment {
		if strings.HasPrefix(v, "IEMobile") {
			p.mobile = true
			return
		}
	}
}

// Guess the OS, the localization and if this is a mobile device
// for Opera.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func opera(p *UserAgent, comment []string) {
	slen := len(comment)

	if strings.HasPrefix(comment[0], "Windows") {
		p.platform = "Windows"
		p.os = normalizeOS(comment[0])
		if slen > 2 {
			if slen > 3 && strings.HasPrefix(comment[2], "MRA") {
				p.localization = comment[3]
			} else {
				p.localization = comment[2]
			}
		}
	} else {
		if strings.HasPrefix(comment[0], "Android") {
			p.mobile = true
		}
		p.platform = comment[0]
		if slen > 1 {
			p.os = comment[1]
			if slen > 3 {
				p.localization = comment[3]
			}
		} else {
			p.os = comment[0]
		}
	}
}

// Given the comment of the first section of the UserAgent string,
// get the platform.
func getPlatform(comment []string) string {
	if len(comment) > 0 {
		if comment[0] != "compatible" {
			if strings.HasPrefix(comment[0], "Windows") {
				return "Windows"
			} else if strings.HasPrefix(comment[0], "Symbian") {
				return "Symbian"
			} else if strings.HasPrefix(comment[0], "webOS") {
				return "webOS"
			} else if comment[0] == "BB10" {
				return "BlackBerry"
			}
			return comment[0]
		}
	}
	return ""
}

// Detect some properties of the OS from the given section.
func (p *UserAgent) detectOS(s section) {
	if s.name == "Mozilla" {
		// Get the platform here. Be aware that IE11 provides a new format
		// that is not backwards-compatible with previous versions of IE.
		p.platform = getPlatform(s.comment)
		if p.platform == "Windows" && len(s.comment) > 0 {
			p.os = normalizeOS(s.comment[0])
		}

		// And finally get the OS depending on the engine.
		switch p.browser.Engine {
		case "":
			p.undecided = true
		case "Gecko":
			gecko(p, s.comment)
		case "AppleWebKit":
			webkit(p, s.comment)
		case "Trident":
			trident(p, s.comment)
		}
	} else if s.name == "Opera" {
		if len(s.comment) > 0 {
			opera(p, s.comment)
		}
	} else {
		// Check whether this is a bot or just a weird browser.
		p.undecided = true
	}
}

// Returns a string containing the platform..
func (p *UserAgent) Platform() string {
	return p.platform
}

// Returns a string containing the name of the Operating System.
func (p *UserAgent) OS() string {
	return p.os
}

// Returns a string containing the localization.
func (p *UserAgent) Localization() string {
	return p.localization
}
<commit_msg>Added support for Windows 10 in the normalizeOS function.<commit_after>// Copyright (C) 2012-2014 Miquel Sabaté Solà <mikisabate@gmail.com>
// This file is licensed under the MIT license.
// See the LICENSE file.

package user_agent

import "strings"

// Normalize the name of the operating system. By now, this just
// affects to Windows.
//
// Returns a string containing the normalized name for the Operating System.
func normalizeOS(name string) string {
	sp := strings.SplitN(name, " ", 3)
	if len(sp) != 3 {
		return name
	}

	switch sp[2] {
	case "5.0":
		return "Windows 2000"
	case "5.01":
		return "Windows 2000, Service Pack 1 (SP1)"
	case "5.1":
		return "Windows XP"
	case "5.2":
		return "Windows XP x64 Edition"
	case "6.0":
		return "Windows Vista"
	case "6.1":
		return "Windows 7"
	case "6.2":
		return "Windows 8"
	case "6.3":
		return "Windows 8.1"
	case "6.4":
		return "Windows 10"
	}
	return name
}

// Guess the OS, the localization and if this is a mobile device for a
// Webkit-powered browser.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func webkit(p *UserAgent, comment []string) {
	if p.platform == "webOS" {
		p.browser.Name = p.platform
		p.os = "Palm"
		if len(comment) > 2 {
			p.localization = comment[2]
		}
		p.mobile = true
	} else if p.platform == "Symbian" {
		p.mobile = true
		p.browser.Name = p.platform
		p.os = comment[0]
	} else if p.platform == "Linux" {
		p.mobile = true
		if p.browser.Name == "Safari" {
			p.browser.Name = "Android"
		}
		if len(comment) > 1 {
			if comment[1] == "U" {
				if len(comment) > 2 {
					p.os = comment[2]
				} else {
					p.mobile = false
					p.os = comment[0]
				}
			} else {
				p.os = comment[1]
			}
		}
		if len(comment) > 3 {
			p.localization = comment[3]
		}
	} else if len(comment) > 0 {
		if len(comment) > 3 {
			p.localization = comment[3]
		}
		if strings.HasPrefix(comment[0], "Windows NT") {
			p.os = normalizeOS(comment[0])
		} else if len(comment) < 2 {
			p.localization = comment[0]
		} else if len(comment) < 3 {
			if !p.googleBot() {
				p.os = normalizeOS(comment[1])
			}
		} else {
			p.os = normalizeOS(comment[2])
		}
		if p.platform == "BlackBerry" {
			p.browser.Name = p.platform
			if p.os == "Touch" {
				p.os = p.platform
			}
		}
	}
}

// Guess the OS, the localization and if this is a mobile device
// for a Gecko-powered browser.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func gecko(p *UserAgent, comment []string) {
	if len(comment) > 1 {
		if comment[1] == "U" {
			if len(comment) > 2 {
				p.os = normalizeOS(comment[2])
			} else {
				p.os = normalizeOS(comment[1])
			}
		} else {
			if p.platform == "Android" {
				p.mobile = true
				p.platform, p.os = normalizeOS(comment[1]), p.platform
			} else if comment[0] == "Mobile" || comment[0] == "Tablet" {
				p.mobile = true
				p.os = "FirefoxOS"
			} else {
				if p.os == "" {
					p.os = normalizeOS(comment[1])
				}
			}
		}
		if len(comment) > 3 {
			p.localization = comment[3]
		}
	}
}

// Guess the OS, the localization and if this is a mobile device
// for Internet Explorer.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func trident(p *UserAgent, comment []string) {
	// Internet Explorer only runs on Windows.
	p.platform = "Windows"

	// The OS can be set before to handle a new case in IE11.
	if p.os == "" {
		if len(comment) > 2 {
			p.os = normalizeOS(comment[2])
		} else {
			p.os = "Windows NT 4.0"
		}
	}

	// Last but not least, let's detect if it comes from a mobile device.
	for _, v := range comment {
		if strings.HasPrefix(v, "IEMobile") {
			p.mobile = true
			return
		}
	}
}

// Guess the OS, the localization and if this is a mobile device
// for Opera.
//
// The first argument p is a reference to the current UserAgent and the second
// argument is a slice of strings containing the comment.
func opera(p *UserAgent, comment []string) {
	slen := len(comment)

	if strings.HasPrefix(comment[0], "Windows") {
		p.platform = "Windows"
		p.os = normalizeOS(comment[0])
		if slen > 2 {
			if slen > 3 && strings.HasPrefix(comment[2], "MRA") {
				p.localization = comment[3]
			} else {
				p.localization = comment[2]
			}
		}
	} else {
		if strings.HasPrefix(comment[0], "Android") {
			p.mobile = true
		}
		p.platform = comment[0]
		if slen > 1 {
			p.os = comment[1]
			if slen > 3 {
				p.localization = comment[3]
			}
		} else {
			p.os = comment[0]
		}
	}
}

// Given the comment of the first section of the UserAgent string,
// get the platform.
func getPlatform(comment []string) string {
	if len(comment) > 0 {
		if comment[0] != "compatible" {
			if strings.HasPrefix(comment[0], "Windows") {
				return "Windows"
			} else if strings.HasPrefix(comment[0], "Symbian") {
				return "Symbian"
			} else if strings.HasPrefix(comment[0], "webOS") {
				return "webOS"
			} else if comment[0] == "BB10" {
				return "BlackBerry"
			}
			return comment[0]
		}
	}
	return ""
}

// Detect some properties of the OS from the given section.
func (p *UserAgent) detectOS(s section) {
	if s.name == "Mozilla" {
		// Get the platform here. Be aware that IE11 provides a new format
		// that is not backwards-compatible with previous versions of IE.
		p.platform = getPlatform(s.comment)
		if p.platform == "Windows" && len(s.comment) > 0 {
			p.os = normalizeOS(s.comment[0])
		}

		// And finally get the OS depending on the engine.
		switch p.browser.Engine {
		case "":
			p.undecided = true
		case "Gecko":
			gecko(p, s.comment)
		case "AppleWebKit":
			webkit(p, s.comment)
		case "Trident":
			trident(p, s.comment)
		}
	} else if s.name == "Opera" {
		if len(s.comment) > 0 {
			opera(p, s.comment)
		}
	} else {
		// Check whether this is a bot or just a weird browser.
		p.undecided = true
	}
}

// Returns a string containing the platform..
func (p *UserAgent) Platform() string {
	return p.platform
}

// Returns a string containing the name of the Operating System.
func (p *UserAgent) OS() string {
	return p.os
}

// Returns a string containing the localization.
func (p *UserAgent) Localization() string {
	return p.localization
}
<|endoftext|>


<commit_before>// +build int

package main

import (
	"strings"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2"
)

func TestKeys(t *testing.T) {
	testCommands(t,
		succ("SET", "one", "1"),
		succ("SET", "two", "2"),
		succ("SET", "three", "3"),
		succ("SET", "four", "4"),
		succSorted("KEYS", `*o*`),
		succSorted("KEYS", `t??`),
		succSorted("KEYS", `t?*`),
		succSorted("KEYS", `*`),
		succSorted("KEYS", `t*`),
		succSorted("KEYS", `t\*`),
		succSorted("KEYS", `[tf]*`),

		// zero length key
		succ("SET", "", "nothing"),
		succ("GET", ""),

		// Simple failure cases
		fail("KEYS"),
		fail("KEYS", "foo", "bar"),
	)

	testCommands(t,
		succ("SET", "[one]", "1"),
		succ("SET", "two", "2"),
		succSorted("KEYS", `[\[o]*`),
		succSorted("KEYS", `\[*`),
		succSorted("KEYS", `*o*`),
		succSorted("KEYS", `[]*`), // nothing
	)
}

func TestRandom(t *testing.T) {
	testCommands(t,
		succ("RANDOMKEY"),
		// A random key from a DB with a single key. We can test that.
		succ("SET", "one", "1"),
		succ("RANDOMKEY"),

		// Simple failure cases
		fail("RANDOMKEY", "bar"),
	)
}

func TestUnknownCommand(t *testing.T) {
	testCommands(t,
		fail("nosuch"),
		fail("noSUCH"),
		fail("noSUCH", 1, 2, 3),
	)
}

func TestQuit(t *testing.T) {
	testCommands(t,
		succ("QUIT"),
	)
}

func TestExists(t *testing.T) {
	testCommands(t,
		succ("SET", "a", "3"),
		succ("HSET", "b", "c", "d"),
		succ("EXISTS", "a", "b"),
		succ("EXISTS", "a", "b", "q"),
		succ("EXISTS", "a", "b", "b", "b", "a", "q"),

		// Error cases
		fail("EXISTS"),
	)
}

func TestRename(t *testing.T) {
	testCommands(t,
		// No 'a' key
		fail("RENAME", "a", "b"),

		// Move a key with the TTL.
		succ("SET", "a", "3"),
		succ("EXPIRE", "a", "123"),
		succ("SET", "b", "12"),
		succ("RENAME", "a", "b"),
		succ("EXISTS", "a"),
		succ("GET", "a"),
		succ("TYPE", "a"),
		succ("TTL", "a"),
		succ("EXISTS", "b"),
		succ("GET", "b"),
		succ("TYPE", "b"),
		succ("TTL", "b"),

		// move a key without TTL
		succ("SET", "nottl", "3"),
		succ("RENAME", "nottl", "stillnottl"),
		succ("TTL", "nottl"),
		succ("TTL", "stillnottl"),

		// Error cases
		fail("RENAME"),
		fail("RENAME", "a"),
		fail("RENAME", "a", "b", "toomany"),
	)
}

func TestRenamenx(t *testing.T) {
	testCommands(t,
		// No 'a' key
		fail("RENAMENX", "a", "b"),

		succ("SET", "a", "value"),
		succ("SET", "str", "value"),
		succ("RENAMENX", "a", "str"),
		succ("EXISTS", "a"),
		succ("EXISTS", "str"),
		succ("GET", "a"),
		succ("GET", "str"),

		succ("RENAMENX", "a", "nosuch"),
		succ("EXISTS", "a"),
		succ("EXISTS", "nosuch"),

		// Error cases
		fail("RENAMENX"),
		fail("RENAMENX", "a"),
		fail("RENAMENX", "a", "b", "toomany"),
	)
}

func TestScan(t *testing.T) {
	testCommands(t,
		// No keys yet
		succ("SCAN", 0),

		succ("SET", "key", "value"),
		succ("SCAN", 0),
		succ("SCAN", 0, "COUNT", 12),
		succ("SCAN", 0, "cOuNt", 12),

		succ("SET", "anotherkey", "value"),
		succ("SCAN", 0, "MATCH", "anoth*"),
		succ("SCAN", 0, "MATCH", "anoth*", "COUNT", 100),
		succ("SCAN", 0, "COUNT", 100, "MATCH", "anoth*"),

		// Can't really test multiple keys.
		// succ("SET", "key2", "value2"),
		// succ("SCAN", 0),

		// Error cases
		fail("SCAN"),
		fail("SCAN", "noint"),
		fail("SCAN", 0, "COUNT", "noint"),
		fail("SCAN", 0, "COUNT"),
		fail("SCAN", 0, "MATCH"),
		fail("SCAN", 0, "garbage"),
		fail("SCAN", 0, "COUNT", 12, "MATCH", "foo", "garbage"),
	)
}

func TestFastForward(t *testing.T) {
	testMultiCommands(t,
		func(r chan<- command, m *miniredis.Miniredis) {
			r <- succ("SET", "key1", "value")
			r <- succ("SET", "key", "value", "PX", 100)
			r <- succSorted("KEYS", "*")
			time.Sleep(200 * time.Millisecond)
			m.FastForward(200 * time.Millisecond)
			r <- succSorted("KEYS", "*")
		},
	)

	testCommands(t,
		fail("SET", "key1", "value", "PX", -100),
		fail("SET", "key2", "value", "EX", -100),
		fail("SET", "key3", "value", "EX", 0),
		succSorted("KEYS", "*"),

		succ("SET", "key4", "value"),
		succSorted("KEYS", "*"),
		succ("EXPIRE", "key4", -100),
		succSorted("KEYS", "*"),

		succ("SET", "key4", "value"),
		succSorted("KEYS", "*"),
		succ("EXPIRE", "key4", 0),
		succSorted("KEYS", "*"),
	)
}

func TestProto(t *testing.T) {
	testCommands(t,
		succ("ECHO", strings.Repeat("X", 1<<24)),
	)
}

func TestSwapdb(t *testing.T) {
	testCommands(t,
		succ("SET", "key1", "val1"),
		succ("SWAPDB", "0", "1"),
		succ("SELECT", "1"),
		succ("GET", "key1"),

		succ("SWAPDB", "1", "1"),
		succ("GET", "key1"),

		fail("SWAPDB"),
		fail("SWAPDB", 1),
		fail("SWAPDB", 1, 2, 3),
		fail("SWAPDB", "foo", 2),
		fail("SWAPDB", 1, "bar"),
		fail("SWAPDB", "foo", "bar"),
		fail("SWAPDB", -1, 2),
		fail("SWAPDB", 1, -2),
		// fail("SWAPDB", 1, 1000), // miniredis has no upperlimit
	)

	// SWAPDB with transactions
	testClients2(t, func(r1, r2 chan<- command) {
		r1 <- succ("SET", "foo", "foooooo")

		r1 <- succ("MULTI")
		r1 <- succ("SWAPDB", 0, 2)
		r1 <- succ("GET", "foo")
		r2 <- succ("GET", "foo")

		r1 <- succ("EXEC")
		r1 <- succ("GET", "foo")
		r2 <- succ("GET", "foo")
	})
}

func TestDel(t *testing.T) {
	testCommands(t,
		succ("SET", "one", "1"),
		succ("SET", "two", "2"),
		succ("SET", "three", "3"),
		succ("SET", "four", "4"),
		succ("DEL", "one"),
		succSorted("KEYS", "*"),

		succ("DEL", "twoooo"),
		succSorted("KEYS", "*"),

		succ("DEL", "two", "four"),
		succSorted("KEYS", "*"),

		fail("DEL"),
		succSorted("KEYS", "*"),
	)
}

func TestUnlink(t *testing.T) {
	testCommands(t,
		succ("SET", "one", "1"),
		succ("SET", "two", "2"),
		succ("SET", "three", "3"),
		succ("SET", "four", "4"),
		succ("UNLINK", "one"),
		succSorted("KEYS", "*"),

		succ("UNLINK", "twoooo"),
		succSorted("KEYS", "*"),

		succ("UNLINK", "two", "four"),
		succSorted("KEYS", "*"),

		fail("UNLINK"),
		succSorted("KEYS", "*"),
	)
}

func TestTouch(t *testing.T) {
	testCommands(t,
		succ("SET", "a", "some value"),
		succ("TOUCH", "a"),
		succ("GET", "a"),
		succ("TTL", "a"),

		succ("TOUCH", "a", "foobar", "a"),

		fail("TOUCH"),
	)
}
<commit_msg>minimal integration test for PERSIST<commit_after>// +build int

package main

import (
	"strings"
	"testing"
	"time"

	"github.com/alicebob/miniredis/v2"
)

func TestKeys(t *testing.T) {
	testCommands(t,
		succ("SET", "one", "1"),
		succ("SET", "two", "2"),
		succ("SET", "three", "3"),
		succ("SET", "four", "4"),
		succSorted("KEYS", `*o*`),
		succSorted("KEYS", `t??`),
		succSorted("KEYS", `t?*`),
		succSorted("KEYS", `*`),
		succSorted("KEYS", `t*`),
		succSorted("KEYS", `t\*`),
		succSorted("KEYS", `[tf]*`),

		// zero length key
		succ("SET", "", "nothing"),
		succ("GET", ""),

		// Simple failure cases
		fail("KEYS"),
		fail("KEYS", "foo", "bar"),
	)

	testCommands(t,
		succ("SET", "[one]", "1"),
		succ("SET", "two", "2"),
		succSorted("KEYS", `[\[o]*`),
		succSorted("KEYS", `\[*`),
		succSorted("KEYS", `*o*`),
		succSorted("KEYS", `[]*`), // nothing
	)
}

func TestRandom(t *testing.T) {
	testCommands(t,
		succ("RANDOMKEY"),
		// A random key from a DB with a single key. We can test that.
		succ("SET", "one", "1"),
		succ("RANDOMKEY"),

		// Simple failure cases
		fail("RANDOMKEY", "bar"),
	)
}

func TestUnknownCommand(t *testing.T) {
	testCommands(t,
		fail("nosuch"),
		fail("noSUCH"),
		fail("noSUCH", 1, 2, 3),
	)
}

func TestQuit(t *testing.T) {
	testCommands(t,
		succ("QUIT"),
	)
}

func TestExists(t *testing.T) {
	testCommands(t,
		succ("SET", "a", "3"),
		succ("HSET", "b", "c", "d"),
		succ("EXISTS", "a", "b"),
		succ("EXISTS", "a", "b", "q"),
		succ("EXISTS", "a", "b", "b", "b", "a", "q"),

		// Error cases
		fail("EXISTS"),
	)
}

func TestRename(t *testing.T) {
	testCommands(t,
		// No 'a' key
		fail("RENAME", "a", "b"),

		// Move a key with the TTL.
		succ("SET", "a", "3"),
		succ("EXPIRE", "a", "123"),
		succ("SET", "b", "12"),
		succ("RENAME", "a", "b"),
		succ("EXISTS", "a"),
		succ("GET", "a"),
		succ("TYPE", "a"),
		succ("TTL", "a"),
		succ("EXISTS", "b"),
		succ("GET", "b"),
		succ("TYPE", "b"),
		succ("TTL", "b"),

		// move a key without TTL
		succ("SET", "nottl", "3"),
		succ("RENAME", "nottl", "stillnottl"),
		succ("TTL", "nottl"),
		succ("TTL", "stillnottl"),

		// Error cases
		fail("RENAME"),
		fail("RENAME", "a"),
		fail("RENAME", "a", "b", "toomany"),
	)
}

func TestRenamenx(t *testing.T) {
	testCommands(t,
		// No 'a' key
		fail("RENAMENX", "a", "b"),

		succ("SET", "a", "value"),
		succ("SET", "str", "value"),
		succ("RENAMENX", "a", "str"),
		succ("EXISTS", "a"),
		succ("EXISTS", "str"),
		succ("GET", "a"),
		succ("GET", "str"),

		succ("RENAMENX", "a", "nosuch"),
		succ("EXISTS", "a"),
		succ("EXISTS", "nosuch"),

		// Error cases
		fail("RENAMENX"),
		fail("RENAMENX", "a"),
		fail("RENAMENX", "a", "b", "toomany"),
	)
}

func TestScan(t *testing.T) {
	testCommands(t,
		// No keys yet
		succ("SCAN", 0),

		succ("SET", "key", "value"),
		succ("SCAN", 0),
		succ("SCAN", 0, "COUNT", 12),
		succ("SCAN", 0, "cOuNt", 12),

		succ("SET", "anotherkey", "value"),
		succ("SCAN", 0, "MATCH", "anoth*"),
		succ("SCAN", 0, "MATCH", "anoth*", "COUNT", 100),
		succ("SCAN", 0, "COUNT", 100, "MATCH", "anoth*"),

		// Can't really test multiple keys.
		// succ("SET", "key2", "value2"),
		// succ("SCAN", 0),

		// Error cases
		fail("SCAN"),
		fail("SCAN", "noint"),
		fail("SCAN", 0, "COUNT", "noint"),
		fail("SCAN", 0, "COUNT"),
		fail("SCAN", 0, "MATCH"),
		fail("SCAN", 0, "garbage"),
		fail("SCAN", 0, "COUNT", 12, "MATCH", "foo", "garbage"),
	)
}

func TestFastForward(t *testing.T) {
	testMultiCommands(t,
		func(r chan<- command, m *miniredis.Miniredis) {
			r <- succ("SET", "key1", "value")
			r <- succ("SET", "key", "value", "PX", 100)
			r <- succSorted("KEYS", "*")
			time.Sleep(200 * time.Millisecond)
			m.FastForward(200 * time.Millisecond)
			r <- succSorted("KEYS", "*")
		},
	)

	testCommands(t,
		fail("SET", "key1", "value", "PX", -100),
		fail("SET", "key2", "value", "EX", -100),
		fail("SET", "key3", "value", "EX", 0),
		succSorted("KEYS", "*"),

		succ("SET", "key4", "value"),
		succSorted("KEYS", "*"),
		succ("EXPIRE", "key4", -100),
		succSorted("KEYS", "*"),

		succ("SET", "key4", "value"),
		succSorted("KEYS", "*"),
		succ("EXPIRE", "key4", 0),
		succSorted("KEYS", "*"),
	)
}

func TestProto(t *testing.T) {
	testCommands(t,
		succ("ECHO", strings.Repeat("X", 1<<24)),
	)
}

func TestSwapdb(t *testing.T) {
	testCommands(t,
		succ("SET", "key1", "val1"),
		succ("SWAPDB", "0", "1"),
		succ("SELECT", "1"),
		succ("GET", "key1"),

		succ("SWAPDB", "1", "1"),
		succ("GET", "key1"),

		fail("SWAPDB"),
		fail("SWAPDB", 1),
		fail("SWAPDB", 1, 2, 3),
		fail("SWAPDB", "foo", 2),
		fail("SWAPDB", 1, "bar"),
		fail("SWAPDB", "foo", "bar"),
		fail("SWAPDB", -1, 2),
		fail("SWAPDB", 1, -2),
		// fail("SWAPDB", 1, 1000), // miniredis has no upperlimit
	)

	// SWAPDB with transactions
	testClients2(t, func(r1, r2 chan<- command) {
		r1 <- succ("SET", "foo", "foooooo")

		r1 <- succ("MULTI")
		r1 <- succ("SWAPDB", 0, 2)
		r1 <- succ("GET", "foo")
		r2 <- succ("GET", "foo")

		r1 <- succ("EXEC")
		r1 <- succ("GET", "foo")
		r2 <- succ("GET", "foo")
	})
}

func TestDel(t *testing.T) {
	testCommands(t,
		succ("SET", "one", "1"),
		succ("SET", "two", "2"),
		succ("SET", "three", "3"),
		succ("SET", "four", "4"),
		succ("DEL", "one"),
		succSorted("KEYS", "*"),

		succ("DEL", "twoooo"),
		succSorted("KEYS", "*"),

		succ("DEL", "two", "four"),
		succSorted("KEYS", "*"),

		fail("DEL"),
		succSorted("KEYS", "*"),
	)
}

func TestUnlink(t *testing.T) {
	testCommands(t,
		succ("SET", "one", "1"),
		succ("SET", "two", "2"),
		succ("SET", "three", "3"),
		succ("SET", "four", "4"),
		succ("UNLINK", "one"),
		succSorted("KEYS", "*"),

		succ("UNLINK", "twoooo"),
		succSorted("KEYS", "*"),

		succ("UNLINK", "two", "four"),
		succSorted("KEYS", "*"),

		fail("UNLINK"),
		succSorted("KEYS", "*"),
	)
}

func TestTouch(t *testing.T) {
	testCommands(t,
		succ("SET", "a", "some value"),
		succ("TOUCH", "a"),
		succ("GET", "a"),
		succ("TTL", "a"),

		succ("TOUCH", "a", "foobar", "a"),

		fail("TOUCH"),
	)
}

func TestPersist(t *testing.T) {
	testCommands(t,
		succ("SET", "foo", "bar"),
		succ("EXPIRE", "foo", 12),
		succ("TTL", "foo"),
		succ("PERSIST", "foo"),
		succ("TTL", "foo"),
	)
}
<|endoftext|>


<commit_before>// Package rk4 provides an integrator based on the fourth-order Runge–Kutta
// method.
//
// https://en.wikipedia.org/wiki/Runge–Kutta_methods
package rk4

// Compute integrates the system of differential equations dy/dx = f(x, y) with
// the initial condition y(x₀) = y₀. The solution is returned at n equidistant
// points starting from and including x₀ with the step size Δx.
func Compute(dydx func(float64, []float64, []float64), y0 []float64,
	x0, Δx float64, n uint) []float64 {

	nd, ns := len(y0), int(n)

	solution := make([]float64, ns*nd)
	copy(solution, y0)

	z := make([]float64, nd)
	f1 := make([]float64, nd)
	f2 := make([]float64, nd)
	f3 := make([]float64, nd)
	f4 := make([]float64, nd)

	for k, x, y := 1, x0, y0; k < ns; k++ {
		// Step 1
		dydx(x, y, f1)

		// Step 2
		for i := 0; i < nd; i++ {
			z[i] = y[i] + Δx*f1[i]/2
		}
		dydx(x+Δx/2, z, f2)

		// Step 3
		for i := 0; i < nd; i++ {
			z[i] = y[i] + Δx*f2[i]/2
		}
		dydx(x+Δx/2, z, f3)

		// Step 4
		for i := 0; i < nd; i++ {
			z[i] = y[i] + Δx*f3[i]
		}
		dydx(x+Δx, z, f4)

		ynew := solution[k*nd:]
		for i := 0; i < nd; i++ {
			ynew[i] = y[i] + Δx*(f1[i]+2*f2[i]+2*f3[i]+f4[i])/6
		}

		x += Δx
		y = ynew
	}

	return solution
}
<commit_msg>Adjusted the description of rk4<commit_after>// Package rk4 provides an integrator of system of ordinary differential
// equations based on the fourth-order Runge–Kutta method.
//
// https://en.wikipedia.org/wiki/Runge–Kutta_methods
package rk4

// Compute integrates the system of differential equations dy/dx = f(x, y) with
// the initial condition y(x₀) = y₀. The solution is returned at n equidistant
// points starting from and including x₀ with the step size Δx.
func Compute(dydx func(float64, []float64, []float64), y0 []float64,
	x0, Δx float64, n uint) []float64 {

	nd, ns := len(y0), int(n)

	solution := make([]float64, ns*nd)
	copy(solution, y0)

	z := make([]float64, nd)
	f1 := make([]float64, nd)
	f2 := make([]float64, nd)
	f3 := make([]float64, nd)
	f4 := make([]float64, nd)

	for k, x, y := 1, x0, y0; k < ns; k++ {
		// Step 1
		dydx(x, y, f1)

		// Step 2
		for i := 0; i < nd; i++ {
			z[i] = y[i] + Δx*f1[i]/2
		}
		dydx(x+Δx/2, z, f2)

		// Step 3
		for i := 0; i < nd; i++ {
			z[i] = y[i] + Δx*f2[i]/2
		}
		dydx(x+Δx/2, z, f3)

		// Step 4
		for i := 0; i < nd; i++ {
			z[i] = y[i] + Δx*f3[i]
		}
		dydx(x+Δx, z, f4)

		ynew := solution[k*nd:]
		for i := 0; i < nd; i++ {
			ynew[i] = y[i] + Δx*(f1[i]+2*f2[i]+2*f3[i]+f4[i])/6
		}

		x += Δx
		y = ynew
	}

	return solution
}
<|endoftext|>


<commit_before>// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
	"bytes"
	"net/http"
	"net/url"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestCreateFile(t *testing.T) {
	prepareTestEnv(t)

	session := loginUser(t, "user2", "password")

	// Request editor page
	req, err := http.NewRequest("GET", "/user2/repo1/_new/master/", nil)
	assert.NoError(t, err)
	resp := session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	doc, err := NewHtmlParser(resp.Body)
	assert.NoError(t, err)
	lastCommit := doc.GetInputValueByName("last_commit")
	assert.NotEmpty(t, lastCommit)

	// Save new file to master branch
	req, err = http.NewRequest("POST", "/user2/repo1/_new/master/",
		bytes.NewBufferString(url.Values{
			"_csrf":         []string{doc.GetInputValueByName("_csrf")},
			"last_commit":   []string{lastCommit},
			"tree_path":     []string{"test.txt"},
			"content":       []string{"Content"},
			"commit_choice": []string{"direct"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusFound, resp.HeaderCode)
}

func TestCreateFileOnProtectedBranch(t *testing.T) {
	prepareTestEnv(t)

	session := loginUser(t, "user2", "password")

	// Open repository branch settings
	req, err := http.NewRequest("GET", "/user2/repo1/settings/branches", nil)
	assert.NoError(t, err)
	resp := session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	doc, err := NewHtmlParser(resp.Body)
	assert.NoError(t, err)

	// Change master branch to protected
	req, err = http.NewRequest("POST", "/user2/repo1/settings/branches?action=protected_branch",
		bytes.NewBufferString(url.Values{
			"_csrf":      []string{doc.GetInputValueByName("_csrf")},
			"branchName": []string{"master"},
			"canPush":    []string{"true"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
	// Check if master branch has been locked successfully
	flashCookie := session.GetCookie("macaron_flash")
	assert.NotNil(t, flashCookie)
	assert.EqualValues(t, flashCookie.Value, "success%3Dmaster%2BLocked%2Bsuccessfully")

	// Request editor page
	req, err = http.NewRequest("GET", "/user2/repo1/_new/master/", nil)
	assert.NoError(t, err)
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	doc, err = NewHtmlParser(resp.Body)
	assert.NoError(t, err)
	lastCommit := doc.GetInputValueByName("last_commit")
	assert.NotEmpty(t, lastCommit)

	// Save new file to master branch
	req, err = http.NewRequest("POST", "/user2/repo1/_new/master/",
		bytes.NewBufferString(url.Values{
			"_csrf":         []string{doc.GetInputValueByName("_csrf")},
			"last_commit":   []string{lastCommit},
			"tree_path":     []string{"test.txt"},
			"content":       []string{"Content"},
			"commit_choice": []string{"direct"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
	// Check body for error message
	assert.Contains(t, string(resp.Body), "Can not commit to protected branch 'master'.")
}
<commit_msg>Add integration test for file editing (#1907)<commit_after>// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
	"bytes"
	"net/http"
	"net/url"
	"path"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestCreateFile(t *testing.T) {
	prepareTestEnv(t)

	session := loginUser(t, "user2", "password")

	// Request editor page
	req, err := http.NewRequest("GET", "/user2/repo1/_new/master/", nil)
	assert.NoError(t, err)
	resp := session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	doc, err := NewHtmlParser(resp.Body)
	assert.NoError(t, err)
	lastCommit := doc.GetInputValueByName("last_commit")
	assert.NotEmpty(t, lastCommit)

	// Save new file to master branch
	req, err = http.NewRequest("POST", "/user2/repo1/_new/master/",
		bytes.NewBufferString(url.Values{
			"_csrf":         []string{doc.GetInputValueByName("_csrf")},
			"last_commit":   []string{lastCommit},
			"tree_path":     []string{"test.txt"},
			"content":       []string{"Content"},
			"commit_choice": []string{"direct"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusFound, resp.HeaderCode)
}

func TestCreateFileOnProtectedBranch(t *testing.T) {
	prepareTestEnv(t)

	session := loginUser(t, "user2", "password")

	// Open repository branch settings
	req, err := http.NewRequest("GET", "/user2/repo1/settings/branches", nil)
	assert.NoError(t, err)
	resp := session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	doc, err := NewHtmlParser(resp.Body)
	assert.NoError(t, err)

	// Change master branch to protected
	req, err = http.NewRequest("POST", "/user2/repo1/settings/branches?action=protected_branch",
		bytes.NewBufferString(url.Values{
			"_csrf":      []string{doc.GetInputValueByName("_csrf")},
			"branchName": []string{"master"},
			"canPush":    []string{"true"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
	// Check if master branch has been locked successfully
	flashCookie := session.GetCookie("macaron_flash")
	assert.NotNil(t, flashCookie)
	assert.EqualValues(t, flashCookie.Value, "success%3Dmaster%2BLocked%2Bsuccessfully")

	// Request editor page
	req, err = http.NewRequest("GET", "/user2/repo1/_new/master/", nil)
	assert.NoError(t, err)
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	doc, err = NewHtmlParser(resp.Body)
	assert.NoError(t, err)
	lastCommit := doc.GetInputValueByName("last_commit")
	assert.NotEmpty(t, lastCommit)

	// Save new file to master branch
	req, err = http.NewRequest("POST", "/user2/repo1/_new/master/",
		bytes.NewBufferString(url.Values{
			"_csrf":         []string{doc.GetInputValueByName("_csrf")},
			"last_commit":   []string{lastCommit},
			"tree_path":     []string{"test.txt"},
			"content":       []string{"Content"},
			"commit_choice": []string{"direct"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
	// Check body for error message
	assert.Contains(t, string(resp.Body), "Can not commit to protected branch 'master'.")
}

func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePath string) {

	newContent := "Hello, World (Edited)\n"

	// Get to the 'edit this file' page
	req, err := http.NewRequest("GET", path.Join(user, repo, "_edit", branch, filePath), nil)
	assert.NoError(t, err)
	resp := session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)

	htmlDoc, err := NewHtmlParser(resp.Body)
	assert.NoError(t, err)
	lastCommit := htmlDoc.GetInputValueByName("last_commit")
	assert.NotEmpty(t, lastCommit)

	// Submit the edits
	req, err = http.NewRequest("POST", path.Join(user, repo, "_edit", branch, filePath),
		bytes.NewBufferString(url.Values{
			"_csrf":         []string{htmlDoc.GetInputValueByName("_csrf")},
			"last_commit":   []string{lastCommit},
			"tree_path":     []string{filePath},
			"content":       []string{newContent},
			"commit_choice": []string{"direct"},
		}.Encode()),
	)
	assert.NoError(t, err)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusFound, resp.HeaderCode)

	// Verify the change
	req, err = http.NewRequest("GET", path.Join(user, repo, "raw", branch, filePath), nil)
	assert.NoError(t, err)
	resp = session.MakeRequest(t, req)
	assert.EqualValues(t, http.StatusOK, resp.HeaderCode)
	assert.EqualValues(t, newContent, string(resp.Body))
}

func TestEditFile(t *testing.T) {
	prepareTestEnv(t)
	session := loginUser(t, "user2", "password")
	testEditFile(t, session, "user2", "repo1", "master", "README.md")
}
<|endoftext|>


<commit_before>//Copyright 2014 Aaron Goldman. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file

package fuse

import (
	"log"
	"os"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	//"bazil.org/fuse/fs/fstestutil"
)

var running bool

//Start mounts the local seed on the local file system
func Start() {
	//fstestutil.DebugByDefault()
	go startFSintegration()
	running = true
}

func ccfsUnmount(mountpoint string) {
	err := fuse.Unmount(mountpoint)
	if err != nil {
		log.Printf("Could not unmount: %s", err)
	}
	log.Printf("Exit-kill program")
	os.Exit(0)
}

func generateInode(NodeID fuse.NodeID, name string) fuse.NodeID {
	return fuse.NodeID(fs.GenerateDynamicInode(uint64(NodeID), name))
}
<commit_msg>Prompt user to add themselves to fuse user group. Can build/run CCFS. need to test on new user.<commit_after>//Copyright 2014 Aaron Goldman. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file

package fuse

import (
	"log"
	"os"
	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	//"bazil.org/fuse/fs/fstestutil"
)

var running bool

//Start mounts the local seed on the local file system
//fuse group id = 104
func Start() {
	//fstestutil.DebugByDefault()
	go checkGroup()
	go startFSintegration()
	running = true
}

func ccfsUnmount(mountpoint string) {
	err := fuse.Unmount(mountpoint)
	if err != nil {
		log.Printf("Could not unmount: %s", err)
	}
	log.Printf("Exit-kill program")
	os.Exit(0)
}

func generateInode(NodeID fuse.NodeID, name string) fuse.NodeID {
	return fuse.NodeID(fs.GenerateDynamicInode(uint64(NodeID), name))
}

func checkGroup() {
	groups, _ := os.Getgroups()
	var fuseGroup bool
	fuseGroup = false
	for i := 0; i < len(groups); i++ {
		if (groups[i]==104) {
			fuseGroup = true
		}
	}
	if fuseGroup==false {
		log.Printf("Add yourself to the fuse usergroup by:\n useradd -G fuse [username]")
	}

}<|endoftext|>


<commit_before>package backoff

import "time"

// Exp provides exponential back off.
type Exp struct {
	Min time.Duration
	Max time.Duration

	count uint32
}

// Wait sleeps using exponential back off.
func (exp *Exp) Wait() {
	d := exp.min() * (1 << exp.count)
	if m := exp.max(); d > m {
		d = m
	}
	time.Sleep(d)
	if exp.count < 31 {
		exp.count++
	}
}

// Reset resets exponential count.
func (exp *Exp) Reset() {
	exp.count = 0
}

func (exp *Exp) min() time.Duration {
	if exp.Min <= 0 {
		return time.Second
	}
	return exp.Min
}

func (exp *Exp) max() time.Duration {
	if exp.Max <= 0 {
		return time.Millisecond
	}
	return exp.Max
}
<commit_msg>swap max and min of backoff<commit_after>package backoff

import "time"

// Exp provides exponential back off.
type Exp struct {
	Min time.Duration
	Max time.Duration

	count uint32
}

// Wait sleeps using exponential back off.
func (exp *Exp) Wait() {
	d := exp.min() * (1 << exp.count)
	if m := exp.max(); d > m {
		d = m
	}
	time.Sleep(d)
	if exp.count < 31 {
		exp.count++
	}
}

// Reset resets exponential count.
func (exp *Exp) Reset() {
	exp.count = 0
}

func (exp *Exp) min() time.Duration {
	if exp.Min <= 0 {
		return time.Millisecond
	}
	return exp.Min
}

func (exp *Exp) max() time.Duration {
	if exp.Max <= 0 {
		return time.Second
	}
	return exp.Max
}
<|endoftext|>


<commit_before>// Copyright 2020 Buf Technologies Inc.
//
// 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 buf

import (
	"context"

	"github.com/bufbuild/buf/internal/pkg/app/appcmd"
	"github.com/bufbuild/buf/internal/pkg/app/appflag"
)

const version = "0.14.0-dev"

// Main is the main.
func Main(use string, options ...RootCommandOption) {
	appcmd.Main(context.Background(), newRootCommand(use, options...), version)
}

// RootCommandOption is an option for a root Command.
type RootCommandOption func(*appcmd.Command, appflag.Builder)
<commit_msg>Update to v0.14.0<commit_after>// Copyright 2020 Buf Technologies Inc.
//
// 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 buf

import (
	"context"

	"github.com/bufbuild/buf/internal/pkg/app/appcmd"
	"github.com/bufbuild/buf/internal/pkg/app/appflag"
)

const version = "0.14.0"

// Main is the main.
func Main(use string, options ...RootCommandOption) {
	appcmd.Main(context.Background(), newRootCommand(use, options...), version)
}

// RootCommandOption is an option for a root Command.
type RootCommandOption func(*appcmd.Command, appflag.Builder)
<|endoftext|>


<commit_before>package status

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Checksum validades if a task is up to date by calculating its source
// files checksum
type Checksum struct {
	Dir       string
	Task      string
	Sources   []string
	Generates []string
	Dry       bool
}

// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
	checksumFile := c.checksumFilePath()

	data, _ := ioutil.ReadFile(checksumFile)
	oldMd5 := strings.TrimSpace(string(data))

	sources, err := globs(c.Dir, c.Sources)
	if err != nil {
		return false, err
	}

	generates, err := glob(c.Dir, c.Generates)
	if err != nil {
		return false, err
	}
	if len(generates) == 0 {
		return false, err
	}
	for _, generate := range generates {
		if _, err := os.Stat(generate); err != nil {
			return false, nil
		}
	}

	newMd5, err := c.checksum(sources...)
	if err != nil {
		return false, nil
	}

	if !c.Dry {
		_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
		if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
			return false, err
		}
	}
	return oldMd5 == newMd5, nil
}

func (c *Checksum) checksum(files ...string) (string, error) {
	h := md5.New()

	for _, f := range files {
		// also sum the filename, so checksum changes for renaming a file
		if _, err := io.Copy(h, strings.NewReader(filepath.Base(f))); err != nil {
			return "", err
		}
		f, err := os.Open(f)
		if err != nil {
			return "", err
		}
		if _, err = io.Copy(h, f); err != nil {
			return "", err
		}
	}

	return fmt.Sprintf("%x", h.Sum(nil)), nil
}

// OnError implements the Checker interface
func (c *Checksum) OnError() error {
	return os.Remove(c.checksumFilePath())
}

func (c *Checksum) checksumFilePath() string {
	return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
}

var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")

// replaces invalid caracters on filenames with "-"
func (*Checksum) normalizeFilename(f string) string {
	return checksumFilenameRegexp.ReplaceAllString(f, "-")
}
<commit_msg>Fix Checksum.IsUpToDate<commit_after>package status

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Checksum validades if a task is up to date by calculating its source
// files checksum
type Checksum struct {
	Dir       string
	Task      string
	Sources   []string
	Generates []string
	Dry       bool
}

// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
	checksumFile := c.checksumFilePath()

	data, _ := ioutil.ReadFile(checksumFile)
	oldMd5 := strings.TrimSpace(string(data))

	sources, err := globs(c.Dir, c.Sources)
	if err != nil {
		return false, nil
	}
	newMd5, err := c.checksum(sources...)
	if err != nil {
		return false, nil
	}

	if !c.Dry {
		_ = os.MkdirAll(filepath.Join(c.Dir, ".task", "checksum"), 0755)
		if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
			return false, err
		}
	}

	if len(c.Generates) != 0 {
		// For each specified 'generates' field, check whether the files actually exist.
		for _, g := range c.Generates {
			generates, err := glob(c.Dir, g)
			if err != nil {
				return false, nil
			}
			if len(generates) == 0 {
				return false, nil
			}
		}
	}

	return oldMd5 == newMd5, nil
}

func (c *Checksum) checksum(files ...string) (string, error) {
	h := md5.New()

	for _, f := range files {
		// also sum the filename, so checksum changes for renaming a file
		if _, err := io.Copy(h, strings.NewReader(filepath.Base(f))); err != nil {
			return "", err
		}
		f, err := os.Open(f)
		if err != nil {
			return "", err
		}
		if _, err = io.Copy(h, f); err != nil {
			return "", err
		}
	}

	return fmt.Sprintf("%x", h.Sum(nil)), nil
}

// OnError implements the Checker interface
func (c *Checksum) OnError() error {
	return os.Remove(c.checksumFilePath())
}

func (c *Checksum) checksumFilePath() string {
	return filepath.Join(c.Dir, ".task", "checksum", c.normalizeFilename(c.Task))
}

var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")

// replaces invalid caracters on filenames with "-"
func (*Checksum) normalizeFilename(f string) string {
	return checksumFilenameRegexp.ReplaceAllString(f, "-")
}
<|endoftext|>


<commit_before>package summary

import (
	"strings"

	"github.com/go-task/task/v2/internal/logger"
	"github.com/go-task/task/v2/internal/taskfile"
)

func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
	for i, call := range c {
		printSpaceBetweenSummaries(l, i)
		PrintTask(l, t.Tasks[call.Task])
	}
}

func printSpaceBetweenSummaries(l *logger.Logger, i int) {
	spaceRequired := i > 0
	if !spaceRequired {
		return
	}

	l.Outf("")
	l.Outf("")
}

func PrintTask(l *logger.Logger, t *taskfile.Task) {
	printTaskName(l, t)
	if hasSummary(t) {
		printTaskSummary(l, t)
	} else if hasDescription(t) {
		printTaskDescription(l, t)
	} else {
		printNoDescriptionOrSummary(l)
	}
	printTaskDependencies(l, t)
	printTaskCommands(l, t)
}

func hasSummary(t *taskfile.Task) bool {
	return t.Summary != ""
}

func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
	lines := strings.Split(t.Summary, "\n")
	for i, line := range lines {
		notLastLine := i+1 < len(lines)
		if notLastLine || line != "" {
			l.Outf(line)
		}
	}
}

func printTaskName(l *logger.Logger, t *taskfile.Task) {
	l.Outf("task: %s", t.Task)
	l.Outf("")
}

func hasDescription(t *taskfile.Task) bool {
	return t.Desc != ""
}

func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
	l.Outf(t.Desc)
}

func printNoDescriptionOrSummary(l *logger.Logger) {
	l.Outf("(task does not have description or summary)")
}

func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
	if len(t.Deps) == 0 {
		return
	}

	l.Outf("")
	l.Outf("dependencies:")

	for _, d := range t.Deps {
		l.Outf(" - %s", d.Task)
	}
}

func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
	if len(t.Cmds) == 0 {
		return
	}

	l.Outf("")
	l.Outf("commands:")
	for _, c := range t.Cmds {
		isCommand := c.Cmd != ""
		if isCommand {
			l.Outf(" - %s", c.Cmd)
		} else {
			l.Outf(" - Task: %s", c.Task)
		}
	}
}
<commit_msg>refactoring<commit_after>package summary

import (
	"strings"

	"github.com/go-task/task/v2/internal/logger"
	"github.com/go-task/task/v2/internal/taskfile"
)

func PrintTasks(l *logger.Logger, t *taskfile.Taskfile, c []taskfile.Call) {
	for i, call := range c {
		printSpaceBetweenSummaries(l, i)
		PrintTask(l, t.Tasks[call.Task])
	}
}

func printSpaceBetweenSummaries(l *logger.Logger, i int) {
	spaceRequired := i > 0
	if !spaceRequired {
		return
	}

	l.Outf("")
	l.Outf("")
}

func PrintTask(l *logger.Logger, t *taskfile.Task) {
	printTaskName(l, t)
	printTaskDescribingText(t, l)
	printTaskDependencies(l, t)
	printTaskCommands(l, t)
}

func printTaskDescribingText(t *taskfile.Task, l *logger.Logger) {
	if hasSummary(t) {
		printTaskSummary(l, t)
	} else if hasDescription(t) {
		printTaskDescription(l, t)
	} else {
		printNoDescriptionOrSummary(l)
	}
}

func hasSummary(t *taskfile.Task) bool {
	return t.Summary != ""
}

func printTaskSummary(l *logger.Logger, t *taskfile.Task) {
	lines := strings.Split(t.Summary, "\n")
	for i, line := range lines {
		notLastLine := i+1 < len(lines)
		if notLastLine || line != "" {
			l.Outf(line)
		}
	}
}

func printTaskName(l *logger.Logger, t *taskfile.Task) {
	l.Outf("task: %s", t.Task)
	l.Outf("")
}

func hasDescription(t *taskfile.Task) bool {
	return t.Desc != ""
}

func printTaskDescription(l *logger.Logger, t *taskfile.Task) {
	l.Outf(t.Desc)
}

func printNoDescriptionOrSummary(l *logger.Logger) {
	l.Outf("(task does not have description or summary)")
}

func printTaskDependencies(l *logger.Logger, t *taskfile.Task) {
	if len(t.Deps) == 0 {
		return
	}

	l.Outf("")
	l.Outf("dependencies:")

	for _, d := range t.Deps {
		l.Outf(" - %s", d.Task)
	}
}

func printTaskCommands(l *logger.Logger, t *taskfile.Task) {
	if len(t.Cmds) == 0 {
		return
	}

	l.Outf("")
	l.Outf("commands:")
	for _, c := range t.Cmds {
		isCommand := c.Cmd != ""
		if isCommand {
			l.Outf(" - %s", c.Cmd)
		} else {
			l.Outf(" - Task: %s", c.Task)
		}
	}
}
<|endoftext|>


<commit_before>package engine

import (
    "cgl.tideland.biz/applog"
    "fmt"
)

type Packet interface {
    GameReaderWriter
    Handle(Commander)
}

type InvalidPacketIdError byte

func (ppe InvalidPacketIdError) Error() string {
    return fmt.Sprintf("Invalid packet id: %d", ppe)
}

type LoginPacket struct {
    Username string
    Token string
}

func (packet *LoginPacket) Handle(comm Commander) {
    applog.Debugf("Login request from %s with token %s",
        packet.Username, packet.Token)
}

func (packet *LoginPacket) UnmarshalGame(data []byte) error {
    return Deserialize(data, &packet)
}

func (packet *LoginPacket) MarshalGame() []byte {
    return Serialize(packet)
}

type MapPacket struct {
    Chunks []*GameChunk
}

//TODO: Get chunks visible to player
func MakeMapPacket(chunks [][]*GameChunk) *MapPacket {
    packetChunks := make([]*GameChunk, 1)
    packetChunks[0] = chunks[0][0]
    return &MapPacket{ packetChunks }
}

func (packet *MapPacket) MarshalGame() []byte {
    return Serialize(packet)
}

type PlayerPacket struct {
    x int
    y int
    name string
}

func MakePlayerPacket(p *Player) *PlayerPacket {
    return &PlayerPacket{ p.Position.X, p.Position.Y, p.name }
}

func (packet *PlayerPacket) MarshalGame() []byte {
    return Serialize(packet)
}

func initializePacketStructures() map[byte] Packet {
    structs := make(map [byte] Packet)
    structs[0] = new(LoginPacket)
    return structs
}

var (
    packetStructs = initializePacketStructures()
)
<commit_msg>Export playerpacket fields for serialization<commit_after>package engine

import (
    "cgl.tideland.biz/applog"
    "fmt"
)

type Packet interface {
    GameReaderWriter
    Handle(Commander)
}

type InvalidPacketIdError byte

func (ppe InvalidPacketIdError) Error() string {
    return fmt.Sprintf("Invalid packet id: %d", ppe)
}

type LoginPacket struct {
    Username string
    Token string
}

func (packet *LoginPacket) Handle(comm Commander) {
    applog.Debugf("Login request from %s with token %s",
        packet.Username, packet.Token)
}

func (packet *LoginPacket) UnmarshalGame(data []byte) error {
    return Deserialize(data, &packet)
}

func (packet *LoginPacket) MarshalGame() []byte {
    return Serialize(packet)
}

type MapPacket struct {
    Chunks []*GameChunk
}

//TODO: Get chunks visible to player
func MakeMapPacket(chunks [][]*GameChunk) *MapPacket {
    packetChunks := make([]*GameChunk, 1)
    packetChunks[0] = chunks[0][0]
    return &MapPacket{ packetChunks }
}

func (packet *MapPacket) MarshalGame() []byte {
    return Serialize(packet)
}

type PlayerPacket struct {
    X int
    Y int
    Name string
}

func MakePlayerPacket(p *Player) *PlayerPacket {
    return &PlayerPacket{ p.Position.X, p.Position.Y, p.name }
}

func (packet *PlayerPacket) MarshalGame() []byte {
    return Serialize(packet)
}

func initializePacketStructures() map[byte] Packet {
    structs := make(map [byte] Packet)
    structs[0] = new(LoginPacket)
    return structs
}

var (
    packetStructs = initializePacketStructures()
)
<|endoftext|>


<commit_before>package worker_test

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"code.cloudfoundry.org/lager/lagertest"
	"github.com/concourse/concourse/atc/worker/gclient"
	"github.com/concourse/concourse/worker"
	"github.com/concourse/concourse/worker/workerfakes"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/ghttp"
)

var _ = Describe("Container Sweeper", func() {
	const (
		sweepInterval              = 50 * time.Millisecond
		maxInFlight                = uint16(1)
		gardenClientTimeoutRequest = 5 * time.Millisecond
	)

	var (
		garden *ghttp.Server

		testLogger = lagertest.NewTestLogger("container-sweeper")

		fakeTSAClient workerfakes.FakeTSAClient
		gardenClient  gclient.Client

		sweeper *worker.ContainerSweeper

		osSignal chan os.Signal
		exited   chan struct{}
	)

	BeforeEach(func() {
		garden = ghttp.NewServer()

		osSignal = make(chan os.Signal)
		exited = make(chan struct{})

		gardenAddr := fmt.Sprintf("http://%s", garden.Addr())
		gardenClient = gclient.BasicGardenClientWithRequestTimeout(testLogger, gardenClientTimeoutRequest, gardenAddr)

		fakeTSAClient = workerfakes.FakeTSAClient{}
		fakeTSAClient.ReportContainersReturns(nil)

		sweeper = worker.NewContainerSweeper(testLogger, sweepInterval, &fakeTSAClient, gardenClient, maxInFlight)

	})

	JustBeforeEach(func() {
		go func() {
			_ = sweeper.Run(osSignal, make(chan struct{}))
			close(exited)
		}()
	})

	AfterEach(func() {
		close(osSignal)
		<-exited
		garden.Close()
	})

	Context("when garden doesn't respond on DELETE", func() {
		var (
			gardenContext context.Context
			gardenCancel  context.CancelFunc
		)

		BeforeEach(func() {
			gardenContext, gardenCancel = context.WithCancel(context.Background())
			garden.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-1"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-2"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-3"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-4"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
			)
			// First GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(0, []string{"some-handle-1", "some-handle-2"}, nil)
			// Second GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(1, []string{"some-handle-3", "some-handle-4"}, nil)

			garden.AllowUnhandledRequests = true

		})
		AfterEach(func() {
			gardenCancel()
		})

		It("request to garden times out eventually", func() {
			Eventually(testLogger.Buffer()).Should(gbytes.Say("failed-to-destroy-container\".*net/http: request canceled \\(Client.Timeout exceeded while awaiting headers\\)"))
		})
		It("sweeper continues ticking and GC'ing", func() {
			// ensure all 4 DELETEs are issues over 2 successive ticks
			Eventually(func() []string {
				// Gather all containers deleted
				var deleteRequestPaths []string
				for _, req := range garden.ReceivedRequests() {
					if req.Method == http.MethodDelete {
						deleteRequestPaths = append(deleteRequestPaths, req.RequestURI)
					}
				}
				return deleteRequestPaths
			}).Should(ConsistOf(
				"/containers/some-handle-1",
				"/containers/some-handle-2",
				"/containers/some-handle-3",
				"/containers/some-handle-4"))

			// Check calls to TSA for containers to destroy > 1
			Expect(fakeTSAClient.ContainersToDestroyCallCount()).To(BeNumerically(">=", 2))
		})
	})

})
<commit_msg>worker: fix container-sweeper test<commit_after>package worker_test

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"code.cloudfoundry.org/lager/lagertest"
	"github.com/concourse/concourse/atc/worker/gclient"
	"github.com/concourse/concourse/worker"
	"github.com/concourse/concourse/worker/workerfakes"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/ghttp"
)

var _ = Describe("Container Sweeper", func() {
	const (
		sweepInterval              = 50 * time.Millisecond
		maxInFlight                = uint16(1)
		gardenClientTimeoutRequest = 5 * time.Millisecond
	)

	var (
		garden *ghttp.Server

		testLogger = lagertest.NewTestLogger("container-sweeper")

		fakeTSAClient workerfakes.FakeTSAClient
		gardenClient  gclient.Client

		sweeper *worker.ContainerSweeper

		osSignal chan os.Signal
		exited   chan struct{}
	)

	BeforeEach(func() {
		garden = ghttp.NewServer()

		osSignal = make(chan os.Signal)
		exited = make(chan struct{})

		gardenAddr := fmt.Sprintf("http://%s", garden.Addr())
		gardenClient = gclient.BasicGardenClientWithRequestTimeout(testLogger, gardenClientTimeoutRequest, gardenAddr)

		fakeTSAClient = workerfakes.FakeTSAClient{}
		fakeTSAClient.ReportContainersReturns(nil)

		sweeper = worker.NewContainerSweeper(testLogger, sweepInterval, &fakeTSAClient, gardenClient, maxInFlight)

	})

	JustBeforeEach(func() {
		go func() {
			_ = sweeper.Run(osSignal, make(chan struct{}))
			close(exited)
		}()
	})

	AfterEach(func() {
		close(osSignal)
		<-exited
		garden.Close()
	})

	Context("when garden doesn't respond on DELETE", func() {
		var (
			gardenContext context.Context
			gardenCancel  context.CancelFunc
		)

		BeforeEach(func() {
			gardenContext, gardenCancel = context.WithCancel(context.Background())
			garden.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-1"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-2"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", "/containers"),
					ghttp.RespondWithJSONEncoded(200, []map[string]string{}),
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-3"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("DELETE", "/containers/some-handle-4"),
					func(w http.ResponseWriter, r *http.Request) {
						<-gardenContext.Done()
					},
				),
			)
			// First GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(0, []string{"some-handle-1", "some-handle-2"}, nil)
			// Second GC Tick
			fakeTSAClient.ContainersToDestroyReturnsOnCall(1, []string{"some-handle-3", "some-handle-4"}, nil)

			garden.AllowUnhandledRequests = true

		})
		AfterEach(func() {
			gardenCancel()
		})

		It("request to garden times out eventually", func() {
			Eventually(testLogger.Buffer()).Should(gbytes.Say("failed-to-destroy-container\".*\\(Client.Timeout exceeded while awaiting headers\\)"))
		})
		It("sweeper continues ticking and GC'ing", func() {
			// ensure all 4 DELETEs are issues over 2 successive ticks
			Eventually(func() []string {
				// Gather all containers deleted
				var deleteRequestPaths []string
				for _, req := range garden.ReceivedRequests() {
					if req.Method == http.MethodDelete {
						deleteRequestPaths = append(deleteRequestPaths, req.RequestURI)
					}
				}
				return deleteRequestPaths
			}).Should(ConsistOf(
				"/containers/some-handle-1",
				"/containers/some-handle-2",
				"/containers/some-handle-3",
				"/containers/some-handle-4"))

			// Check calls to TSA for containers to destroy > 1
			Expect(fakeTSAClient.ContainersToDestroyCallCount()).To(BeNumerically(">=", 2))
		})
	})

})
<|endoftext|>


<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package upgrader

import (
	"launchpad.net/juju-core/tools"
)

var RetryAfter = &retryAfter

func EnsureTools(u *Upgrader, agentTools *tools.Tools, disableSSLHostnameVerification bool) error {
	return u.ensureTools(agentTools, disableSSLHostnameVerification)
}
<commit_msg>go fmt<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package upgrader

import (
	"launchpad.net/juju-core/tools"
)

var RetryAfter = &retryAfter

func EnsureTools(u *Upgrader, agentTools *tools.Tools, disableSSLHostnameVerification bool) error {
	return u.ensureTools(agentTools, disableSSLHostnameVerification)
}
<|endoftext|>


<commit_before>package visit

import (
	"github.com/index0h/go-tracker/uuid"
	"github.com/index0h/go-tracker/visit/entity"
)

type Repository interface {
	// Find clientID by sessionID
	FindClientID(sessionID uuid.Uuid) (clientID string, error)

	// Find sessionID by clientID
	FindSessionID(clientID string) (sessionID string, error)

	// Verify method MUST check that sessionID is not registered by another not empty clientID
	Verify(sessionID uuid.Uuid, clientID string) (bool, error)

	// Save visit to database
	Insert(*entity.Visit) error
}
<commit_msg>fix interface<commit_after>package visit

import (
	"github.com/index0h/go-tracker/uuid"
	"github.com/index0h/go-tracker/visit/entity"
)

type Repository interface {
	// Find clientID by sessionID
	FindClientID(sessionID uuid.Uuid) (clientID string, error)

	// Find sessionID by clientID
	FindSessionID(clientID string) (sessionID uuid.Uuid, error)

	// Verify method MUST check that sessionID is not registered by another not empty clientID
	Verify(sessionID uuid.Uuid, clientID string) (bool, error)

	// Save visit to database
	Insert(*entity.Visit) error
}
<|endoftext|>