<commit_before>package phlag

import (
	"flag"
	"fmt"
	getcd "github.com/ProductHealth/gommons/etcd"
	"github.com/coreos/go-etcd/etcd"
	"github.com/fatih/structs"
	"log"
	"os"
	"reflect"
	"strconv"
	"time"
)

var flagSet = flag.CommandLine
var flagSetArgs = os.Args[1:]
var durationKind = reflect.TypeOf(time.Nanosecond).Kind()

const (
	etcdTag        = "etcd"
	phlagTag       = "phlag"
	descriptionTag = "description"
)

type Phlag struct {
	client           etcdClient
	etcdPathTemplate string // Etcd location of param, for example '/company.com/config/%v'

}

// Logger function, replace by your preferred implementation
var Logger func(string, ...interface{}) = log.Printf

// Minimal interface definition around etcd client, allows creation of fake in tests
type etcdClient interface {
	Get(string, bool, bool) (*etcd.Response, error)
}

func New(template string) (*Phlag, error) {
	client, _ := getcd.NewEtcdClient()
	return NewWithClient(client, template), nil
}

func NewWithClient(client etcdClient, template string) *Phlag {
	return &Phlag{client, template}
}

// Get the named parameter from either the cli or etcd
func (e *Phlag) Get(name, etcdPath string) *string {
	if flagGiven(flagSet, name) {
		valueFromCli := flagSet.Lookup(name)
		Logger("Using command line value %v for param %v", valueFromCli.Value.String(), name)
		cliValue := valueFromCli.Value.String()
		return &cliValue
	}

	if e.client == nil {
		return nil
	}

	// No command line param given, lookup through etcd
	// Logger("Fetching param %v from etcd", name)
	if etcdPath == "" {
		etcdPath = fmt.Sprintf(e.etcdPathTemplate, name)
	}
	// Logger("Using etc path %v", etcdPath)
	valueFromEtcd, err := e.client.Get(etcdPath, false, false)
	if err != nil { // TODO : Sort out '100: Key not found' messages
		Logger(err.Error())
		return nil
	}
	if valueFromEtcd.Node != nil {
		// Logger("Returing node value %v", valueFromEtcd.Node.Value)
		Logger("Using etcd value %v for param %v", valueFromEtcd.Node.Value, name)
		return &valueFromEtcd.Node.Value
	}
	return nil
}

func (e *Phlag) Resolve(target interface{}) {
	s := structs.New(target)
	for _, field := range s.Fields() {
		configuredName := field.Tag(phlagTag)
		if configuredName == "" {
			continue
		}
		description := field.Tag(descriptionTag)
		switch field.Kind() {
		case durationKind:
			flagSet.String(configuredName, field.Value().(time.Duration).String(), description)
		case reflect.String:
			flagSet.String(configuredName, field.Value().(string), description)
		case reflect.Int:
			flagSet.Int(configuredName, field.Value().(int), description)
		}

	}
	flagSet.Parse(flagSetArgs)
	for _, field := range s.Fields() {
		configuredName := field.Tag(phlagTag)
		if configuredName == "" {
			continue
		}
		etcdPath := field.Tag(etcdTag)
		resolvedValue := e.Get(configuredName, etcdPath)
		if resolvedValue == nil {
			Logger("Cannot resolve field %v using cli params or etcd", configuredName)
			continue
		}
		// Logger("Field %v is of type %v, setting resolved value %v", field.Name(), field.Kind().String(), *resolvedValue)
		var err error
		switch {
		case field.Kind() == durationKind:
			v := *resolvedValue
			d, err := time.ParseDuration(v)
			if err == nil {
				err = field.Set(d)
			}
		case field.Kind() == reflect.String:
			v := *resolvedValue
			err = field.Set(v)
		case field.Kind() == reflect.Int:
			v, _ := strconv.Atoi(*resolvedValue)
			err = field.Set(v)
		case field.Kind() == reflect.Int32:
			v, _ := strconv.Atoi(*resolvedValue)
			err = field.Set(v)
		case field.Kind() == reflect.Int64:
			v, _ := strconv.Atoi(*resolvedValue)
			err = field.Set(v)
		default:
			Logger("Unable to handle reflect.Kind : %v", field.Kind())
		}
		if err != nil {
			Logger("Could not set field %v, encoutered error %v", field.Name(), err.Error())
		}
		//Logger("Field %v now has value %v", field.Name(), field.Value())
	}
}

func flagGiven(flagSet *flag.FlagSet, name string) bool {
	var flags = []string{}
	flagSet.Visit(func(f *flag.Flag) { flags = append(flags, f.Name) })
	return stringInSlice(name, flags)
}

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}
<commit_msg>Added logging<commit_after>package phlag

import (
	"flag"
	"fmt"
	getcd "github.com/ProductHealth/gommons/etcd"
	"github.com/coreos/go-etcd/etcd"
	"github.com/fatih/structs"
	"log"
	"os"
	"reflect"
	"strconv"
	"time"
)

var flagSet = flag.CommandLine
var flagSetArgs = os.Args[1:]
var durationKind = reflect.TypeOf(time.Nanosecond).Kind()

const (
	etcdTag        = "etcd"
	phlagTag       = "phlag"
	descriptionTag = "description"
)

type Phlag struct {
	client           etcdClient
	etcdPathTemplate string // Etcd location of param, for example '/company.com/config/%v'

}

// Logger function, replace by your preferred implementation
var Logger func(string, ...interface{}) = log.Printf

// Minimal interface definition around etcd client, allows creation of fake in tests
type etcdClient interface {
	Get(string, bool, bool) (*etcd.Response, error)
}

func New(template string) (*Phlag, error) {
	client, _ := getcd.NewEtcdClient()
	Logger("ETCD CLIENT: %#v", client)
	return NewWithClient(client, template), nil
}

func NewWithClient(client etcdClient, template string) *Phlag {
	return &Phlag{client, template}
}

// Get the named parameter from either the cli or etcd
func (e *Phlag) Get(name, etcdPath string) *string {
	if flagGiven(flagSet, name) {
		valueFromCli := flagSet.Lookup(name)
		Logger("Using command line value %v for param %v", valueFromCli.Value.String(), name)
		cliValue := valueFromCli.Value.String()
		return &cliValue
	}

	if e.client == nil {
		return nil
	}

	// No command line param given, lookup through etcd
	// Logger("Fetching param %v from etcd", name)
	if etcdPath == "" {
		etcdPath = fmt.Sprintf(e.etcdPathTemplate, name)
	}
	// Logger("Using etc path %v", etcdPath)
	valueFromEtcd, err := e.client.Get(etcdPath, false, false)
	if err != nil { // TODO : Sort out '100: Key not found' messages
		Logger(err.Error())
		return nil
	}
	if valueFromEtcd.Node != nil {
		// Logger("Returing node value %v", valueFromEtcd.Node.Value)
		Logger("Using etcd value %v for param %v", valueFromEtcd.Node.Value, name)
		return &valueFromEtcd.Node.Value
	}
	return nil
}

func (e *Phlag) Resolve(target interface{}) {
	s := structs.New(target)
	for _, field := range s.Fields() {
		configuredName := field.Tag(phlagTag)
		if configuredName == "" {
			continue
		}
		description := field.Tag(descriptionTag)
		switch field.Kind() {
		case durationKind:
			flagSet.String(configuredName, field.Value().(time.Duration).String(), description)
		case reflect.String:
			flagSet.String(configuredName, field.Value().(string), description)
		case reflect.Int:
			flagSet.Int(configuredName, field.Value().(int), description)
		}

	}
	flagSet.Parse(flagSetArgs)
	for _, field := range s.Fields() {
		configuredName := field.Tag(phlagTag)
		if configuredName == "" {
			continue
		}
		etcdPath := field.Tag(etcdTag)
		resolvedValue := e.Get(configuredName, etcdPath)
		if resolvedValue == nil {
			Logger("Cannot resolve field %v using cli params or etcd", configuredName)
			continue
		}
		// Logger("Field %v is of type %v, setting resolved value %v", field.Name(), field.Kind().String(), *resolvedValue)
		var err error
		switch {
		case field.Kind() == durationKind:
			v := *resolvedValue
			d, err := time.ParseDuration(v)
			if err == nil {
				err = field.Set(d)
			}
		case field.Kind() == reflect.String:
			v := *resolvedValue
			err = field.Set(v)
		case field.Kind() == reflect.Int:
			v, _ := strconv.Atoi(*resolvedValue)
			err = field.Set(v)
		case field.Kind() == reflect.Int32:
			v, _ := strconv.Atoi(*resolvedValue)
			err = field.Set(v)
		case field.Kind() == reflect.Int64:
			v, _ := strconv.Atoi(*resolvedValue)
			err = field.Set(v)
		default:
			Logger("Unable to handle reflect.Kind : %v", field.Kind())
		}
		if err != nil {
			Logger("Could not set field %v, encoutered error %v", field.Name(), err.Error())
		}
		//Logger("Field %v now has value %v", field.Name(), field.Value())
	}
}

func flagGiven(flagSet *flag.FlagSet, name string) bool {
	var flags = []string{}
	flagSet.Visit(func(f *flag.Flag) { flags = append(flags, f.Name) })
	return stringInSlice(name, flags)
}

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/taskcluster/httpbackoff"
	tcclient "github.com/taskcluster/taskcluster-client-go"
	"github.com/taskcluster/taskcluster-client-go/queue"
)

var (
	// for overriding/complementing system mime type mappings
	customMimeMappings map[string]string = map[string]string{

		// keys *must* be lower-case

		".log": "text/plain",
	}
)

type (
	Artifact interface {
		ProcessResponse(response interface{}) error
		RequestObject() interface{}
		ResponseObject() interface{}
		Base() *BaseArtifact
	}

	BaseArtifact struct {
		CanonicalPath string
		Name          string
		Expires       tcclient.Time
	}

	S3Artifact struct {
		*BaseArtifact
		MimeType        string
		ContentEncoding string
	}

	AzureArtifact struct {
		*BaseArtifact
		MimeType string
	}

	RedirectArtifact struct {
		*BaseArtifact
		MimeType string
		URL      string
	}

	ErrorArtifact struct {
		*BaseArtifact
		Message string
		Reason  string
	}
)

func (base *BaseArtifact) Base() *BaseArtifact {
	return base
}

func (artifact *RedirectArtifact) ProcessResponse(response interface{}) error {
	// nothing to do
	return nil
}

func (redirectArtifact *RedirectArtifact) RequestObject() interface{} {
	return &queue.RedirectArtifactRequest{
		ContentType: redirectArtifact.MimeType,
		Expires:     redirectArtifact.Expires,
		StorageType: "reference",
		URL:         redirectArtifact.URL,
	}
}

func (redirectArtifact *RedirectArtifact) ResponseObject() interface{} {
	return new(queue.RedirectArtifactResponse)
}

func (artifact *ErrorArtifact) ProcessResponse(response interface{}) error {
	// TODO: process error response
	return nil
}

func (errArtifact *ErrorArtifact) RequestObject() interface{} {
	return &queue.ErrorArtifactRequest{
		Expires:     errArtifact.Expires,
		Message:     errArtifact.Message,
		Reason:      errArtifact.Reason,
		StorageType: "error",
	}
}

func (errArtifact *ErrorArtifact) ResponseObject() interface{} {
	return new(queue.ErrorArtifactResponse)
}

func (errArtifact *ErrorArtifact) String() string {
	return fmt.Sprintf("%q", *errArtifact)
}

// createTempFileForPUTBody gzip-compresses the file at path rawContentFile and writes
// it to a temporary file. The file path of the generated temporary file is returned.
// It is the responsibility of the caller to delete the temporary file.
func (artifact *S3Artifact) CreateTempFileForPUTBody() string {
	rawContentFile := filepath.Join(taskContext.TaskDir, artifact.CanonicalPath)
	baseName := filepath.Base(rawContentFile)
	tmpFile, err := ioutil.TempFile("", baseName)
	if err != nil {
		log.Fatal(err)
	}
	defer tmpFile.Close()
	var target io.Writer = tmpFile
	if artifact.ContentEncoding == "gzip" {
		gzipLogWriter := gzip.NewWriter(tmpFile)
		defer gzipLogWriter.Close()
		gzipLogWriter.Name = baseName
		target = gzipLogWriter
	}
	source, err := os.Open(rawContentFile)
	if err != nil {
		panic(err)
	}
	defer source.Close()
	io.Copy(target, source)
	return tmpFile.Name()
}

func (artifact *S3Artifact) ChooseContentEncoding() {
	// respect value, if already set
	if artifact.ContentEncoding != "" {
		return
	}
	// based on https://github.com/evansd/whitenoise/blob/03f6ea846394e01cbfe0c730141b81eb8dd6e88a/whitenoise/compress.py#L21-L29
	SKIP_COMPRESS_EXTENSIONS := map[string]bool{
		// Images
		".jpg":  true,
		".jpeg": true,
		".png":  true,
		".gif":  true,
		".webp": true,
		// Compressed files
		".zip": true,
		".gz":  true,
		".tgz": true,
		".bz2": true,
		".tbz": true,
		// Flash
		".swf": true,
		".flv": true,
		// Fonts
		".woff":  true,
		".woff2": true,
	}
	if SKIP_COMPRESS_EXTENSIONS[filepath.Ext(artifact.CanonicalPath)] {
		return
	}

	artifact.ContentEncoding = "gzip"
}

func (artifact *S3Artifact) ProcessResponse(resp interface{}) (err error) {
	response := resp.(*queue.S3ArtifactResponse)

	artifact.ChooseContentEncoding()
	transferContentFile := artifact.CreateTempFileForPUTBody()
	defer os.Remove(transferContentFile)

	// perform http PUT to upload to S3...
	httpClient := &http.Client{}
	httpCall := func() (*http.Response, error, error) {
		transferContent, err := os.Open(transferContentFile)
		if err != nil {
			return nil, nil, err
		}
		defer transferContent.Close()
		transferContentFileInfo, err := transferContent.Stat()
		if err != nil {
			return nil, nil, err
		}
		transferContentLength := transferContentFileInfo.Size()

		httpRequest, err := http.NewRequest("PUT", response.PutURL, transferContent)
		if err != nil {
			return nil, nil, err
		}
		httpRequest.Header.Set("Content-Type", artifact.MimeType)
		httpRequest.ContentLength = transferContentLength
		if enc := artifact.ContentEncoding; enc != "" {
			httpRequest.Header.Set("Content-Encoding", enc)
		}
		requestHeaders, dumpError := httputil.DumpRequestOut(httpRequest, false)
		if dumpError != nil {
			log.Print("Could not dump request, never mind...")
		} else {
			log.Print("Request")
			log.Print(string(requestHeaders))
		}
		putResp, err := httpClient.Do(httpRequest)
		return putResp, err, nil
	}
	putResp, putAttempts, err := httpbackoff.Retry(httpCall)
	log.Printf("%v put requests issued to %v", putAttempts, response.PutURL)
	if putResp != nil {
		defer putResp.Body.Close()
		respBody, dumpError := httputil.DumpResponse(putResp, true)
		if dumpError != nil {
			log.Print("Could not dump response output, never mind...")
		} else {
			log.Print("Response")
			log.Print(string(respBody))
		}
	}
	return err
}

func (s3Artifact *S3Artifact) RequestObject() interface{} {
	return &queue.S3ArtifactRequest{
		ContentType: s3Artifact.MimeType,
		Expires:     s3Artifact.Expires,
		StorageType: "s3",
	}
}

func (s3Artifact *S3Artifact) ResponseObject() interface{} {
	return new(queue.S3ArtifactResponse)
}

func (s3Artifact *S3Artifact) String() string {
	return fmt.Sprintf("%q", *s3Artifact)
}

// Returns the artifacts as listed in the payload of the task (note this does
// not include log files)
func (task *TaskRun) PayloadArtifacts() []Artifact {
	artifacts := make([]Artifact, 0)
	for _, artifact := range task.Payload.Artifacts {
		base := &BaseArtifact{
			CanonicalPath: canonicalPath(artifact.Path),
			Name:          artifact.Name,
			Expires:       artifact.Expires,
		}
		// if no name given, use canonical path
		if base.Name == "" {
			base.Name = base.CanonicalPath
		}
		// default expiry should be task expiry
		if time.Time(base.Expires).IsZero() {
			base.Expires = task.Definition.Expires
		}
		switch artifact.Type {
		case "file":
			artifacts = append(artifacts, resolve(base, "file"))
		case "directory":
			if errArtifact := resolve(base, "directory"); errArtifact != nil {
				artifacts = append(artifacts, errArtifact)
				continue
			}
			walkFn := func(path string, info os.FileInfo, incomingErr error) error {
				// I think we don't need to handle incomingErr != nil since
				// resolve(...) gets called which should catch the same issues
				// raised in incomingErr - *** I GUESS *** !!
				subPath, err := filepath.Rel(taskContext.TaskDir, path)
				if err != nil {
					// this indicates a bug in the code
					panic(err)
				}
				relativePath, err := filepath.Rel(base.CanonicalPath, subPath)
				if err != nil {
					// this indicates a bug in the code
					panic(err)
				}
				subName := filepath.Join(base.Name, relativePath)
				b := &BaseArtifact{
					CanonicalPath: canonicalPath(subPath),
					Name:          canonicalPath(subName),
					Expires:       artifact.Expires,
				}
				switch {
				case info.IsDir():
					if errArtifact := resolve(b, "directory"); errArtifact != nil {
						artifacts = append(artifacts, errArtifact)
					}
				default:
					artifacts = append(artifacts, resolve(b, "file"))
				}
				return nil
			}
			filepath.Walk(filepath.Join(taskContext.TaskDir, base.CanonicalPath), walkFn)
		}
	}
	return artifacts
}

// File should be resolved as an S3Artifact if file exists as file and is
// readable, otherwise i) if it does not exist or ii) cannot be read, as a
// "file-missing-on-worker" ErrorArtifact, otherwise if it exists as a
// directory, as "invalid-resource-on-worker" ErrorArtifact. A directory should
// resolve as `nil` if directory exists as directory and is readable, otherwise
// i) if it does not exist or ii) cannot be read, as a "file-missing-on-worker"
// ErrorArtifact, otherwise if it exists as a file, as
// "invalid-resource-on-worker" ErrorArtifact
// TODO: need to also handle "too-large-file-on-worker"
func resolve(base *BaseArtifact, artifactType string) Artifact {
	fullPath := filepath.Join(taskContext.TaskDir, base.CanonicalPath)
	fileReader, err := os.Open(fullPath)
	if err != nil {
		// cannot read file/dir, create an error artifact
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("Could not read %s '%s'", artifactType, fullPath),
			Reason:       "file-missing-on-worker",
		}
	}
	defer fileReader.Close()
	// ok it exists, but is it right type?
	fileinfo, err := fileReader.Stat()
	if err != nil {
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("Could not stat %s '%s'", artifactType, fullPath),
			Reason:       "invalid-resource-on-worker",
		}
	}
	if artifactType == "file" && fileinfo.IsDir() {
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("File artifact '%s' exists as a directory, not a file, on the worker", fullPath),
			Reason:       "invalid-resource-on-worker",
		}
	}
	if artifactType == "directory" && !fileinfo.IsDir() {
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("Directory artifact '%s' exists as a file, not a directory, on the worker", fullPath),
			Reason:       "invalid-resource-on-worker",
		}
	}
	if artifactType == "directory" {
		return nil
	}
	extension := filepath.Ext(base.CanonicalPath)
	// first look up our own custom mime type mappings
	mimeType := customMimeMappings[strings.ToLower(extension)]
	// then fall back to system mime type mappings
	if mimeType == "" {
		mimeType = mime.TypeByExtension(extension)
	}
	// lastly, fall back to application/octet-stream in the absense of any other value
	if mimeType == "" {
		// application/octet-stream is the mime type for "unknown"
		mimeType = "application/octet-stream"
	}
	return &S3Artifact{
		BaseArtifact: base,
		MimeType:     mimeType,
	}
}

// The Queue expects paths to use a forward slash, so let's make sure we have a
// way to generate a path in this format
func canonicalPath(path string) string {
	if os.PathSeparator == '/' {
		return path
	}
	return strings.Replace(path, string(os.PathSeparator), "/", -1)
}

func (task *TaskRun) uploadLog(logFile string) *CommandExecutionError {
	return task.uploadArtifact(
		&S3Artifact{
			BaseArtifact: &BaseArtifact{
				CanonicalPath: logFile,
				Name:          logFile,
				// logs expire when task expires
				Expires: task.Definition.Expires,
			},
			MimeType:        "text/plain; charset=utf-8",
			ContentEncoding: "gzip",
		},
	)
}

func (task *TaskRun) uploadArtifact(artifact Artifact) *CommandExecutionError {
	task.Logf("Uploading file %v as artifact %v", artifact.Base().CanonicalPath, artifact.Base().Name)
	task.Artifacts = append(task.Artifacts, artifact)
	payload, err := json.Marshal(artifact.RequestObject())
	if err != nil {
		panic(err)
	}
	par := queue.PostArtifactRequest(json.RawMessage(payload))
	parsp, err := task.Queue.CreateArtifact(
		task.TaskID,
		strconv.Itoa(int(task.RunID)),
		artifact.Base().Name,
		&par,
	)
	if err != nil {
		switch t := err.(type) {
		case *os.PathError:
			// artifact does not exist or is not readable...
			task.Logf("Artifact could not be read: %v", err)
			return Failure(err)
		case httpbackoff.BadHttpResponseCode:
			if t.HttpResponseCode/100 == 5 {
				return ResourceUnavailable(fmt.Errorf("TASK EXCEPTION due to response code %v from Queue when uploading artifact %#v", t.HttpResponseCode, artifact))
			} else {
				// if not a 5xx error, then either task cancelled, or a problem with the request == worker bug
				task.StatusManager.UpdateStatus()
				status := task.StatusManager.LastKnownStatus()
				if status == deadlineExceeded || status == cancelled {
					return nil
				}
				panic(fmt.Errorf("WORKER EXCEPTION due to response code %v from Queue when uploading artifact %#v", t.HttpResponseCode, artifact))
			}
		default:
			panic(fmt.Errorf("WORKER EXCEPTION due to non-recoverable error when uploading artifact: %#v", t))
		}
	}
	// unmarshal response into object
	resp := artifact.ResponseObject()
	e := json.Unmarshal(json.RawMessage(*parsp), resp)
	if e != nil {
		panic(e)
	}
	e = artifact.ProcessResponse(resp)
	// note: this only returns an error, if ProcessResponse returns an error...
	if e != nil {
		task.Logf("Error uploading artifact: %v", e)
	}
	return ResourceUnavailable(e)
}
<commit_msg>Don't use gzip content encoding for .7z files<commit_after>package main

import (
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/http/httputil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/taskcluster/httpbackoff"
	tcclient "github.com/taskcluster/taskcluster-client-go"
	"github.com/taskcluster/taskcluster-client-go/queue"
)

var (
	// for overriding/complementing system mime type mappings
	customMimeMappings map[string]string = map[string]string{

		// keys *must* be lower-case

		".log": "text/plain",
	}
)

type (
	Artifact interface {
		ProcessResponse(response interface{}) error
		RequestObject() interface{}
		ResponseObject() interface{}
		Base() *BaseArtifact
	}

	BaseArtifact struct {
		CanonicalPath string
		Name          string
		Expires       tcclient.Time
	}

	S3Artifact struct {
		*BaseArtifact
		MimeType        string
		ContentEncoding string
	}

	AzureArtifact struct {
		*BaseArtifact
		MimeType string
	}

	RedirectArtifact struct {
		*BaseArtifact
		MimeType string
		URL      string
	}

	ErrorArtifact struct {
		*BaseArtifact
		Message string
		Reason  string
	}
)

func (base *BaseArtifact) Base() *BaseArtifact {
	return base
}

func (artifact *RedirectArtifact) ProcessResponse(response interface{}) error {
	// nothing to do
	return nil
}

func (redirectArtifact *RedirectArtifact) RequestObject() interface{} {
	return &queue.RedirectArtifactRequest{
		ContentType: redirectArtifact.MimeType,
		Expires:     redirectArtifact.Expires,
		StorageType: "reference",
		URL:         redirectArtifact.URL,
	}
}

func (redirectArtifact *RedirectArtifact) ResponseObject() interface{} {
	return new(queue.RedirectArtifactResponse)
}

func (artifact *ErrorArtifact) ProcessResponse(response interface{}) error {
	// TODO: process error response
	return nil
}

func (errArtifact *ErrorArtifact) RequestObject() interface{} {
	return &queue.ErrorArtifactRequest{
		Expires:     errArtifact.Expires,
		Message:     errArtifact.Message,
		Reason:      errArtifact.Reason,
		StorageType: "error",
	}
}

func (errArtifact *ErrorArtifact) ResponseObject() interface{} {
	return new(queue.ErrorArtifactResponse)
}

func (errArtifact *ErrorArtifact) String() string {
	return fmt.Sprintf("%q", *errArtifact)
}

// createTempFileForPUTBody gzip-compresses the file at path rawContentFile and writes
// it to a temporary file. The file path of the generated temporary file is returned.
// It is the responsibility of the caller to delete the temporary file.
func (artifact *S3Artifact) CreateTempFileForPUTBody() string {
	rawContentFile := filepath.Join(taskContext.TaskDir, artifact.CanonicalPath)
	baseName := filepath.Base(rawContentFile)
	tmpFile, err := ioutil.TempFile("", baseName)
	if err != nil {
		log.Fatal(err)
	}
	defer tmpFile.Close()
	var target io.Writer = tmpFile
	if artifact.ContentEncoding == "gzip" {
		gzipLogWriter := gzip.NewWriter(tmpFile)
		defer gzipLogWriter.Close()
		gzipLogWriter.Name = baseName
		target = gzipLogWriter
	}
	source, err := os.Open(rawContentFile)
	if err != nil {
		panic(err)
	}
	defer source.Close()
	io.Copy(target, source)
	return tmpFile.Name()
}

func (artifact *S3Artifact) ChooseContentEncoding() {
	// respect value, if already set
	if artifact.ContentEncoding != "" {
		return
	}
	// based on https://github.com/evansd/whitenoise/blob/03f6ea846394e01cbfe0c730141b81eb8dd6e88a/whitenoise/compress.py#L21-L29
	// with .7z added (useful for NSS)
	SKIP_COMPRESS_EXTENSIONS := map[string]bool{
		// Images
		".jpg":  true,
		".jpeg": true,
		".png":  true,
		".gif":  true,
		".webp": true,
		// Compressed files
		".7z":  true,
		".zip": true,
		".gz":  true,
		".tgz": true,
		".bz2": true,
		".tbz": true,
		// Flash
		".swf": true,
		".flv": true,
		// Fonts
		".woff":  true,
		".woff2": true,
	}
	if SKIP_COMPRESS_EXTENSIONS[filepath.Ext(artifact.CanonicalPath)] {
		return
	}

	artifact.ContentEncoding = "gzip"
}

func (artifact *S3Artifact) ProcessResponse(resp interface{}) (err error) {
	response := resp.(*queue.S3ArtifactResponse)

	artifact.ChooseContentEncoding()
	transferContentFile := artifact.CreateTempFileForPUTBody()
	defer os.Remove(transferContentFile)

	// perform http PUT to upload to S3...
	httpClient := &http.Client{}
	httpCall := func() (*http.Response, error, error) {
		transferContent, err := os.Open(transferContentFile)
		if err != nil {
			return nil, nil, err
		}
		defer transferContent.Close()
		transferContentFileInfo, err := transferContent.Stat()
		if err != nil {
			return nil, nil, err
		}
		transferContentLength := transferContentFileInfo.Size()

		httpRequest, err := http.NewRequest("PUT", response.PutURL, transferContent)
		if err != nil {
			return nil, nil, err
		}
		httpRequest.Header.Set("Content-Type", artifact.MimeType)
		httpRequest.ContentLength = transferContentLength
		if enc := artifact.ContentEncoding; enc != "" {
			httpRequest.Header.Set("Content-Encoding", enc)
		}
		requestHeaders, dumpError := httputil.DumpRequestOut(httpRequest, false)
		if dumpError != nil {
			log.Print("Could not dump request, never mind...")
		} else {
			log.Print("Request")
			log.Print(string(requestHeaders))
		}
		putResp, err := httpClient.Do(httpRequest)
		return putResp, err, nil
	}
	putResp, putAttempts, err := httpbackoff.Retry(httpCall)
	log.Printf("%v put requests issued to %v", putAttempts, response.PutURL)
	if putResp != nil {
		defer putResp.Body.Close()
		respBody, dumpError := httputil.DumpResponse(putResp, true)
		if dumpError != nil {
			log.Print("Could not dump response output, never mind...")
		} else {
			log.Print("Response")
			log.Print(string(respBody))
		}
	}
	return err
}

func (s3Artifact *S3Artifact) RequestObject() interface{} {
	return &queue.S3ArtifactRequest{
		ContentType: s3Artifact.MimeType,
		Expires:     s3Artifact.Expires,
		StorageType: "s3",
	}
}

func (s3Artifact *S3Artifact) ResponseObject() interface{} {
	return new(queue.S3ArtifactResponse)
}

func (s3Artifact *S3Artifact) String() string {
	return fmt.Sprintf("%q", *s3Artifact)
}

// Returns the artifacts as listed in the payload of the task (note this does
// not include log files)
func (task *TaskRun) PayloadArtifacts() []Artifact {
	artifacts := make([]Artifact, 0)
	for _, artifact := range task.Payload.Artifacts {
		base := &BaseArtifact{
			CanonicalPath: canonicalPath(artifact.Path),
			Name:          artifact.Name,
			Expires:       artifact.Expires,
		}
		// if no name given, use canonical path
		if base.Name == "" {
			base.Name = base.CanonicalPath
		}
		// default expiry should be task expiry
		if time.Time(base.Expires).IsZero() {
			base.Expires = task.Definition.Expires
		}
		switch artifact.Type {
		case "file":
			artifacts = append(artifacts, resolve(base, "file"))
		case "directory":
			if errArtifact := resolve(base, "directory"); errArtifact != nil {
				artifacts = append(artifacts, errArtifact)
				continue
			}
			walkFn := func(path string, info os.FileInfo, incomingErr error) error {
				// I think we don't need to handle incomingErr != nil since
				// resolve(...) gets called which should catch the same issues
				// raised in incomingErr - *** I GUESS *** !!
				subPath, err := filepath.Rel(taskContext.TaskDir, path)
				if err != nil {
					// this indicates a bug in the code
					panic(err)
				}
				relativePath, err := filepath.Rel(base.CanonicalPath, subPath)
				if err != nil {
					// this indicates a bug in the code
					panic(err)
				}
				subName := filepath.Join(base.Name, relativePath)
				b := &BaseArtifact{
					CanonicalPath: canonicalPath(subPath),
					Name:          canonicalPath(subName),
					Expires:       artifact.Expires,
				}
				switch {
				case info.IsDir():
					if errArtifact := resolve(b, "directory"); errArtifact != nil {
						artifacts = append(artifacts, errArtifact)
					}
				default:
					artifacts = append(artifacts, resolve(b, "file"))
				}
				return nil
			}
			filepath.Walk(filepath.Join(taskContext.TaskDir, base.CanonicalPath), walkFn)
		}
	}
	return artifacts
}

// File should be resolved as an S3Artifact if file exists as file and is
// readable, otherwise i) if it does not exist or ii) cannot be read, as a
// "file-missing-on-worker" ErrorArtifact, otherwise if it exists as a
// directory, as "invalid-resource-on-worker" ErrorArtifact. A directory should
// resolve as `nil` if directory exists as directory and is readable, otherwise
// i) if it does not exist or ii) cannot be read, as a "file-missing-on-worker"
// ErrorArtifact, otherwise if it exists as a file, as
// "invalid-resource-on-worker" ErrorArtifact
// TODO: need to also handle "too-large-file-on-worker"
func resolve(base *BaseArtifact, artifactType string) Artifact {
	fullPath := filepath.Join(taskContext.TaskDir, base.CanonicalPath)
	fileReader, err := os.Open(fullPath)
	if err != nil {
		// cannot read file/dir, create an error artifact
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("Could not read %s '%s'", artifactType, fullPath),
			Reason:       "file-missing-on-worker",
		}
	}
	defer fileReader.Close()
	// ok it exists, but is it right type?
	fileinfo, err := fileReader.Stat()
	if err != nil {
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("Could not stat %s '%s'", artifactType, fullPath),
			Reason:       "invalid-resource-on-worker",
		}
	}
	if artifactType == "file" && fileinfo.IsDir() {
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("File artifact '%s' exists as a directory, not a file, on the worker", fullPath),
			Reason:       "invalid-resource-on-worker",
		}
	}
	if artifactType == "directory" && !fileinfo.IsDir() {
		return &ErrorArtifact{
			BaseArtifact: base,
			Message:      fmt.Sprintf("Directory artifact '%s' exists as a file, not a directory, on the worker", fullPath),
			Reason:       "invalid-resource-on-worker",
		}
	}
	if artifactType == "directory" {
		return nil
	}
	extension := filepath.Ext(base.CanonicalPath)
	// first look up our own custom mime type mappings
	mimeType := customMimeMappings[strings.ToLower(extension)]
	// then fall back to system mime type mappings
	if mimeType == "" {
		mimeType = mime.TypeByExtension(extension)
	}
	// lastly, fall back to application/octet-stream in the absense of any other value
	if mimeType == "" {
		// application/octet-stream is the mime type for "unknown"
		mimeType = "application/octet-stream"
	}
	return &S3Artifact{
		BaseArtifact: base,
		MimeType:     mimeType,
	}
}

// The Queue expects paths to use a forward slash, so let's make sure we have a
// way to generate a path in this format
func canonicalPath(path string) string {
	if os.PathSeparator == '/' {
		return path
	}
	return strings.Replace(path, string(os.PathSeparator), "/", -1)
}

func (task *TaskRun) uploadLog(logFile string) *CommandExecutionError {
	return task.uploadArtifact(
		&S3Artifact{
			BaseArtifact: &BaseArtifact{
				CanonicalPath: logFile,
				Name:          logFile,
				// logs expire when task expires
				Expires: task.Definition.Expires,
			},
			MimeType:        "text/plain; charset=utf-8",
			ContentEncoding: "gzip",
		},
	)
}

func (task *TaskRun) uploadArtifact(artifact Artifact) *CommandExecutionError {
	task.Logf("Uploading file %v as artifact %v", artifact.Base().CanonicalPath, artifact.Base().Name)
	task.Artifacts = append(task.Artifacts, artifact)
	payload, err := json.Marshal(artifact.RequestObject())
	if err != nil {
		panic(err)
	}
	par := queue.PostArtifactRequest(json.RawMessage(payload))
	parsp, err := task.Queue.CreateArtifact(
		task.TaskID,
		strconv.Itoa(int(task.RunID)),
		artifact.Base().Name,
		&par,
	)
	if err != nil {
		switch t := err.(type) {
		case *os.PathError:
			// artifact does not exist or is not readable...
			task.Logf("Artifact could not be read: %v", err)
			return Failure(err)
		case httpbackoff.BadHttpResponseCode:
			if t.HttpResponseCode/100 == 5 {
				return ResourceUnavailable(fmt.Errorf("TASK EXCEPTION due to response code %v from Queue when uploading artifact %#v", t.HttpResponseCode, artifact))
			} else {
				// if not a 5xx error, then either task cancelled, or a problem with the request == worker bug
				task.StatusManager.UpdateStatus()
				status := task.StatusManager.LastKnownStatus()
				if status == deadlineExceeded || status == cancelled {
					return nil
				}
				panic(fmt.Errorf("WORKER EXCEPTION due to response code %v from Queue when uploading artifact %#v", t.HttpResponseCode, artifact))
			}
		default:
			panic(fmt.Errorf("WORKER EXCEPTION due to non-recoverable error when uploading artifact: %#v", t))
		}
	}
	// unmarshal response into object
	resp := artifact.ResponseObject()
	e := json.Unmarshal(json.RawMessage(*parsp), resp)
	if e != nil {
		panic(e)
	}
	e = artifact.ProcessResponse(resp)
	// note: this only returns an error, if ProcessResponse returns an error...
	if e != nil {
		task.Logf("Error uploading artifact: %v", e)
	}
	return ResourceUnavailable(e)
}
<|endoftext|>


<commit_before>// Package libkv provides key value storage for embedded go application.
package libkv

import (
	expire "github.com/jeffjen/go-libkv/timer"

	"sync"
	"time"
)

const (
	GET int = iota
	SET
	DEL
	EXPIRE
	GONE
	GETSET
	LPUSH
	LTRIM
)

// Event delivers keyspace changes to registerd Watcher
type Event struct {
	Action int    `desc: action taken e.g. GET, SET, EXPIRE`
	Iden   string `desc: key that was affected`
}

// avent is a control object for Store event hub to deliver Event
type avent struct {
	c chan<- *Event   `desc: channel to send the event into`
	h <-chan struct{} `desc: control channel to indicate unregister`
}

// kv_avent is holds metadata about how to distribute keyspace Event
type kv_avent struct {
	sync.RWMutex

	src     chan *Event      `desc: Event source channel from keyspace`
	list    map[int64]*avent `desc: list of registerd party for Event`
	halt    chan struct{}    `desc: control channel to stop Event distribution`
	counter int64            `desc: counter to tag Watcher`
}

// thing is an object stored in Store
type thing struct {
	X interface{} `desc: the object to store`
	T *time.Time  `desc: the expiration date on this thing`
}

// store is the actual KV store
type store struct {
	store map[string]thing `desc: the actual KV store`
	index map[string]int64 `desc: the index mapper for key to schdule index`
}

// Store is a simple key value in memory storage.
// Upon initialization, Store provides general get, set, del, list operations,
// as well as the ability to expire a key and watch a key change.
type Store struct {
	sync.RWMutex

	e *expire.Timer `desc: scheduler for keyspace expiration`
	m *store        `desc: the actual store`
	s *kv_avent     `desc: keyspace event hub`
}

func (s *Store) event_hub() (ok <-chan bool) {
	ack := make(chan bool, 1)
	go func() {
		ack <- true
		for yay := true; yay; {
			select {
			case <-s.s.halt:
				yay = false
			case one_event := <-s.s.src:
				s.s.Lock()
				for idx, ev := range s.s.list {
					select {
					case <-ev.h:
						delete(s.s.list, idx)
					case ev.c <- one_event:
						continue
					}
				}
				s.s.Unlock()
			}
		}
	}()
	return ack
}

// init setups the key value storage.
// Upon initialization, the Store spawns expiration scheduler and keyspace event hub.
func (s *Store) init() (ok <-chan bool) {
	s.e.Tic()
	return s.event_hub()
}

// NewStore creates a Store object.
// Store object is fully initialzed upon creation.
func NewStore() (s *Store) {
	s = &Store{
		e: expire.NewTimer(),
		m: &store{
			store: make(map[string]thing),
			index: make(map[string]int64),
		},
		s: &kv_avent{
			src:     make(chan *Event, 8),
			list:    make(map[int64]*avent),
			halt:    make(chan struct{}),
			counter: 1, // initialzed to positive value
		},
	}
	<-s.init() // make sure both scheduler and event hub is running
	return
}

// Close stops the Store scheduler and event hub.
func (s *Store) Close() {
	close(s.s.halt)
	s.e.Toc()
}

func (s *Store) pushEvent(event ...*Event) {
	for _, ev := range event {
		s.s.src <- ev
	}
}

// del removes an item from the Store keyspace
func (s *Store) del(iden string, jobId int64) bool {
	// check key exist
	if _, ok := s.m.store[iden]; !ok {
		return false
	}

	// check key job id; if exist then job id must match
	if jid, ok := s.m.index[iden]; ok && jobId != jid {
		// jobId will ALWAYS be positve value
		if jid == -1 {
			delete(s.m.index, iden)
		}
		return false
	}

	// remove key from store
	delete(s.m.store, iden)
	delete(s.m.index, iden)

	return true
}

// get retrieves an item x identified by iden
func (s *Store) get(iden string) (x interface{}) {
	if obj, ok := s.m.store[iden]; ok {
		x = obj.X
	}
	return
}

// set adds an item to the Store keyspace; sets expiration handler when
// appropriate
func (s *Store) set(iden string, x interface{}, exp *time.Time) bool {
	if idx, ok := s.m.index[iden]; ok {
		s.e.Cancel(idx)
		s.m.index[iden] = -1 // invalidate any fired expire handler
	}
	s.m.store[iden] = thing{X: x, T: exp}
	if exp != nil {
		s.expire(iden, *exp)
	}
	return true
}

func (s *Store) expire(iden string, exp time.Time) {
	id := iden
	s.m.index[iden] = s.e.SchedFunc(exp, func(jobId int64) {
		s.Lock()
		defer s.Unlock()
		if s.del(id, jobId) {
			s.pushEvent(&Event{GONE, iden})
		}
	})
}

// Set puts an aribtrary item x into Store identified by iden
func (s *Store) Set(iden string, x interface{}) (ret bool) {
	s.Lock()
	defer s.Unlock()
	ret = s.set(iden, x, nil)
	if ret {
		s.pushEvent(&Event{SET, iden})
	}
	return
}

// Set puts an aribtrary item x into Store identified by iden to be expired at
// exp
func (s *Store) Setexp(iden string, x interface{}, exp time.Time) (ret bool) {
	s.Lock()
	defer s.Unlock()
	ret = s.set(iden, x, &exp)
	if ret {
		s.pushEvent(&Event{SET, iden}, &Event{EXPIRE, iden})
	}
	return
}

// Get retrieves an item x identified by iden
func (s *Store) Get(iden string) (x interface{}) {
	s.RLock()
	defer s.RUnlock()
	if x = s.get(iden); x != nil {
		if _, ok := x.([]thing); !ok {
			s.pushEvent(&Event{GET, iden})
		} else {
			x = nil
		}
	}
	return
}

// Getset retrieves an item y identified by iden and replace it with item x
func (s *Store) Getset(iden string, x interface{}) (y interface{}) {
	s.Lock()
	defer s.Unlock()
	y = s.get(iden)
	s.set(iden, x, nil)
	s.pushEvent(&Event{GETSET, iden})
	return
}

// Getexp retrieves an item x identified by iden and set expiration
func (s *Store) Getexp(iden string, exp time.Time) (x interface{}) {
	s.Lock()
	defer s.Unlock()
	if x = s.get(iden); x != nil {
		s.pushEvent(&Event{GET, iden})
		s.expire(iden, exp)
	}
	return
}

// TTL reports the life time left on the item identified by iden
func (s *Store) TTL(iden string) (in time.Duration) {
	s.RLock()
	defer s.RUnlock()
	if obj, ok := s.m.store[iden]; ok && obj.T != nil {
		in = obj.T.Sub(time.Now())
		if in < 0 {
			in = time.Duration(0)
		}
	}
	return
}

// Expire puts item identified by iden to expire at exp
func (s *Store) Expire(iden string, exp time.Time) bool {
	s.Lock()
	defer s.Unlock()
	if _, ok := s.m.store[iden]; !ok {
		return false
	} else {
		s.expire(iden, exp)
		s.pushEvent(&Event{EXPIRE, iden})
		return true
	}
}

// Del removes the item identified by iden from Store
func (s *Store) Del(iden string) {
	s.Lock()
	defer s.Unlock()
	jobId, _ := s.m.index[iden]
	if s.del(iden, jobId) {
		s.pushEvent(&Event{DEL, iden})
	}
}

// Lpush appends an item to item identified by iden
// creates new list item
// returns size of the item after operation; -1 for failed attempt
func (s *Store) Lpush(iden string, x interface{}) (size int64) {
	s.Lock()
	defer s.Unlock()
	if obj, ok := s.m.store[iden]; !ok {
		// the "thing" to store is a []thing with one new item
		s.m.store[iden] = thing{
			X: []thing{
				thing{X: x, T: nil},
			},
			T: nil,
		}
		size = 1
		s.pushEvent(&Event{LPUSH, iden})
	} else if lobj, ok := obj.X.([]thing); ok {
		// find the "thing", check that it is a []thing, and append to it
		lobj = append([]thing{thing{X: x, T: nil}}, lobj...)
		s.m.store[iden] = thing{X: lobj, T: obj.T}
		size = int64(len(lobj)) + 1
		s.pushEvent(&Event{LPUSH, iden})
	} else {
		size = -1
	}
	return
}

func (s *Store) rangeidx(start, stop, length int64) (begin, end int64) {
	if start >= length {
		begin = length
	} else if start >= 0 && start < length {
		begin = start
	} else {
		begin = length + start
		if begin < 0 {
			begin = 0
		}
	}
	if stop >= length {
		end = length
	} else if stop >= 0 && stop < length {
		end = stop
	} else {
		end = length + stop + 1
	}
	if end < begin {
		end = begin
	}
	return
}

// Lrange returns a slice of items within start and stop.
func (s *Store) Lrange(iden string, start, stop int64) (items []interface{}) {
	s.Lock()
	defer s.Unlock()
	if obj, ok := s.m.store[iden]; !ok {
		items = nil
	} else if lobj, ok := obj.X.([]thing); !ok {
		items = nil
	} else {
		begin, end := s.rangeidx(start, stop, int64(len(lobj)))
		for _, obj := range lobj[begin:end] {
			items = append(items, obj.X)
		}
	}
	return
}

// Ltrim keeps items specified in start and stop range and remove all other
// items.
// start and stop can be negative values.  If the value is -1, it indicates the
// end of list; if it is greater then the actual length, it is clamped to the
// boundary beteween 0 and length of item
// returns size of the item after operation; -1 for failed attempt
func (s *Store) Ltrim(iden string, start, stop int64) (size int64) {
	s.Lock()
	defer s.Unlock()
	if obj, ok := s.m.store[iden]; !ok {
		size = -1
	} else if lobj, ok := obj.X.([]thing); !ok {
		size = -1
	} else {
		begin, end := s.rangeidx(start, stop, int64(len(lobj)))
		lobj = lobj[begin:end]
		s.m.store[iden] = thing{X: lobj, T: obj.T}
		size = int64(len(lobj))
		s.pushEvent(&Event{LTRIM, iden})
	}
	return
}

// register takes a control object avent and place it into the Wather list in
// keyspace event hub.
func (s *Store) register(inn *avent) {
	s.s.Lock()
	defer s.s.Unlock()
	r := s.s.counter
	s.s.counter = s.s.counter + 1
	s.s.list[r] = inn
}

// Watch provides interested party to monitor keyspace changes.
// A Watcher (caller of Watch function) provides a stopping condition, and gets
// a channel for future Event in keyspace.
// Stop a Watcher by closeing the stop channel
func (s *Store) Watch(stop <-chan struct{}) <-chan *Event {
	output := make(chan *Event, 8)
	go func() {
		defer close(output)
		incoming := make(chan *Event, 1)
		end := make(chan struct{})
		s.register(&avent{incoming, end})
		defer close(end)
		for yay := true; yay; {
			select {
			case <-stop:
				yay = false
			case ev := <-incoming:
				select {
				default:
					yay = false
				case output <- ev:
					continue
				}
			}
		}
	}()
	return output
}

// Key retrieves the full list of item key in Store.
func (s *Store) Key() (items []string) {
	s.RLock()
	defer s.RUnlock()
	items = make([]string, len(s.m.store))
	idx := 0
	for k, _ := range s.m.store {
		items[idx] = k
		idx++
	}
	return
}

// Keyexp retrieves the full list of item key in Store that has an expiration.
func (s *Store) Keyexp() (items []string) {
	s.RLock()
	defer s.RUnlock()
	items = make([]string, len(s.m.index))
	idx := 0
	for k, _ := range s.m.index {
		items[idx] = k
		idx++
	}
	return
}
<commit_msg>NEW: iterate handler for key store<commit_after>// Package libkv provides key value storage for embedded go application.
package libkv

import (
	expire "github.com/jeffjen/go-libkv/timer"

	"sync"
	"time"
)

const (
	GET int = iota
	SET
	DEL
	EXPIRE
	GONE
	GETSET
	LPUSH
	LTRIM
)

// Event delivers keyspace changes to registerd Watcher
type Event struct {
	Action int    `desc: action taken e.g. GET, SET, EXPIRE`
	Iden   string `desc: key that was affected`
}

// avent is a control object for Store event hub to deliver Event
type avent struct {
	c chan<- *Event   `desc: channel to send the event into`
	h <-chan struct{} `desc: control channel to indicate unregister`
}

// kv_avent is holds metadata about how to distribute keyspace Event
type kv_avent struct {
	sync.RWMutex

	src     chan *Event      `desc: Event source channel from keyspace`
	list    map[int64]*avent `desc: list of registerd party for Event`
	halt    chan struct{}    `desc: control channel to stop Event distribution`
	counter int64            `desc: counter to tag Watcher`
}

// thing is an object stored in Store
type thing struct {
	X interface{} `desc: the object to store`
	T *time.Time  `desc: the expiration date on this thing`
}

// store is the actual KV store
type store struct {
	store map[string]thing `desc: the actual KV store`
	index map[string]int64 `desc: the index mapper for key to schdule index`
}

// Store is a simple key value in memory storage.
// Upon initialization, Store provides general get, set, del, list operations,
// as well as the ability to expire a key and watch a key change.
type Store struct {
	sync.RWMutex

	e *expire.Timer `desc: scheduler for keyspace expiration`
	m *store        `desc: the actual store`
	s *kv_avent     `desc: keyspace event hub`
}

func (s *Store) event_hub() (ok <-chan bool) {
	ack := make(chan bool, 1)
	go func() {
		ack <- true
		for yay := true; yay; {
			select {
			case <-s.s.halt:
				yay = false
			case one_event := <-s.s.src:
				s.s.Lock()
				for idx, ev := range s.s.list {
					select {
					case <-ev.h:
						delete(s.s.list, idx)
					case ev.c <- one_event:
						continue
					}
				}
				s.s.Unlock()
			}
		}
	}()
	return ack
}

// init setups the key value storage.
// Upon initialization, the Store spawns expiration scheduler and keyspace event hub.
func (s *Store) init() (ok <-chan bool) {
	s.e.Tic()
	return s.event_hub()
}

// NewStore creates a Store object.
// Store object is fully initialzed upon creation.
func NewStore() (s *Store) {
	s = &Store{
		e: expire.NewTimer(),
		m: &store{
			store: make(map[string]thing),
			index: make(map[string]int64),
		},
		s: &kv_avent{
			src:     make(chan *Event, 8),
			list:    make(map[int64]*avent),
			halt:    make(chan struct{}),
			counter: 1, // initialzed to positive value
		},
	}
	<-s.init() // make sure both scheduler and event hub is running
	return
}

// Close stops the Store scheduler and event hub.
func (s *Store) Close() {
	close(s.s.halt)
	s.e.Toc()
}

func (s *Store) pushEvent(event ...*Event) {
	for _, ev := range event {
		s.s.src <- ev
	}
}

// del removes an item from the Store keyspace
func (s *Store) del(iden string, jobId int64) bool {
	// check key exist
	if _, ok := s.m.store[iden]; !ok {
		return false
	}

	// check key job id; if exist then job id must match
	if jid, ok := s.m.index[iden]; ok && jobId != jid {
		// jobId will ALWAYS be positve value
		if jid == -1 {
			delete(s.m.index, iden)
		}
		return false
	}

	// remove key from store
	delete(s.m.store, iden)
	delete(s.m.index, iden)

	return true
}

// get retrieves an item x identified by iden
func (s *Store) get(iden string) (x interface{}) {
	if obj, ok := s.m.store[iden]; ok {
		x = obj.X
	}
	return
}

// set adds an item to the Store keyspace; sets expiration handler when
// appropriate
func (s *Store) set(iden string, x interface{}, exp *time.Time) bool {
	if idx, ok := s.m.index[iden]; ok {
		s.e.Cancel(idx)
		s.m.index[iden] = -1 // invalidate any fired expire handler
	}
	s.m.store[iden] = thing{X: x, T: exp}
	if exp != nil {
		s.expire(iden, *exp)
	}
	return true
}

func (s *Store) expire(iden string, exp time.Time) {
	id := iden
	s.m.index[iden] = s.e.SchedFunc(exp, func(jobId int64) {
		s.Lock()
		defer s.Unlock()
		if s.del(id, jobId) {
			s.pushEvent(&Event{GONE, iden})
		}
	})
}

// Set puts an aribtrary item x into Store identified by iden
func (s *Store) Set(iden string, x interface{}) (ret bool) {
	s.Lock()
	defer s.Unlock()
	ret = s.set(iden, x, nil)
	if ret {
		s.pushEvent(&Event{SET, iden})
	}
	return
}

// Set puts an aribtrary item x into Store identified by iden to be expired at
// exp
func (s *Store) Setexp(iden string, x interface{}, exp time.Time) (ret bool) {
	s.Lock()
	defer s.Unlock()
	ret = s.set(iden, x, &exp)
	if ret {
		s.pushEvent(&Event{SET, iden}, &Event{EXPIRE, iden})
	}
	return
}

// Get retrieves an item x identified by iden
func (s *Store) Get(iden string) (x interface{}) {
	s.RLock()
	defer s.RUnlock()
	if x = s.get(iden); x != nil {
		if _, ok := x.([]thing); !ok {
			s.pushEvent(&Event{GET, iden})
		} else {
			x = nil
		}
	}
	return
}

// Getset retrieves an item y identified by iden and replace it with item x
func (s *Store) Getset(iden string, x interface{}) (y interface{}) {
	s.Lock()
	defer s.Unlock()
	y = s.get(iden)
	s.set(iden, x, nil)
	s.pushEvent(&Event{GETSET, iden})
	return
}

// Getexp retrieves an item x identified by iden and set expiration
func (s *Store) Getexp(iden string, exp time.Time) (x interface{}) {
	s.Lock()
	defer s.Unlock()
	if x = s.get(iden); x != nil {
		s.pushEvent(&Event{GET, iden})
		s.expire(iden, exp)
	}
	return
}

// TTL reports the life time left on the item identified by iden
func (s *Store) TTL(iden string) (in time.Duration) {
	s.RLock()
	defer s.RUnlock()
	if obj, ok := s.m.store[iden]; ok && obj.T != nil {
		in = obj.T.Sub(time.Now())
		if in < 0 {
			in = time.Duration(0)
		}
	}
	return
}

// Expire puts item identified by iden to expire at exp
func (s *Store) Expire(iden string, exp time.Time) bool {
	s.Lock()
	defer s.Unlock()
	if _, ok := s.m.store[iden]; !ok {
		return false
	} else {
		s.expire(iden, exp)
		s.pushEvent(&Event{EXPIRE, iden})
		return true
	}
}

// Del removes the item identified by iden from Store
func (s *Store) Del(iden string) {
	s.Lock()
	defer s.Unlock()
	jobId, _ := s.m.index[iden]
	if s.del(iden, jobId) {
		s.pushEvent(&Event{DEL, iden})
	}
}

// Lpush appends an item to item identified by iden
// creates new list item
// returns size of the item after operation; -1 for failed attempt
func (s *Store) Lpush(iden string, x interface{}) (size int64) {
	s.Lock()
	defer s.Unlock()
	if obj, ok := s.m.store[iden]; !ok {
		// the "thing" to store is a []thing with one new item
		s.m.store[iden] = thing{
			X: []thing{
				thing{X: x, T: nil},
			},
			T: nil,
		}
		size = 1
		s.pushEvent(&Event{LPUSH, iden})
	} else if lobj, ok := obj.X.([]thing); ok {
		// find the "thing", check that it is a []thing, and append to it
		lobj = append([]thing{thing{X: x, T: nil}}, lobj...)
		s.m.store[iden] = thing{X: lobj, T: obj.T}
		size = int64(len(lobj)) + 1
		s.pushEvent(&Event{LPUSH, iden})
	} else {
		size = -1
	}
	return
}

func (s *Store) rangeidx(start, stop, length int64) (begin, end int64) {
	if start >= length {
		begin = length
	} else if start >= 0 && start < length {
		begin = start
	} else {
		begin = length + start
		if begin < 0 {
			begin = 0
		}
	}
	if stop >= length {
		end = length
	} else if stop >= 0 && stop < length {
		end = stop
	} else {
		end = length + stop + 1
	}
	if end < begin {
		end = begin
	}
	return
}

// Lrange returns a slice of items within start and stop.
func (s *Store) Lrange(iden string, start, stop int64) (items []interface{}) {
	s.Lock()
	defer s.Unlock()
	if obj, ok := s.m.store[iden]; !ok {
		items = nil
	} else if lobj, ok := obj.X.([]thing); !ok {
		items = nil
	} else {
		begin, end := s.rangeidx(start, stop, int64(len(lobj)))
		for _, obj := range lobj[begin:end] {
			items = append(items, obj.X)
		}
	}
	return
}

// Ltrim keeps items specified in start and stop range and remove all other
// items.
// start and stop can be negative values.  If the value is -1, it indicates the
// end of list; if it is greater then the actual length, it is clamped to the
// boundary beteween 0 and length of item
// returns size of the item after operation; -1 for failed attempt
func (s *Store) Ltrim(iden string, start, stop int64) (size int64) {
	s.Lock()
	defer s.Unlock()
	if obj, ok := s.m.store[iden]; !ok {
		size = -1
	} else if lobj, ok := obj.X.([]thing); !ok {
		size = -1
	} else {
		begin, end := s.rangeidx(start, stop, int64(len(lobj)))
		lobj = lobj[begin:end]
		s.m.store[iden] = thing{X: lobj, T: obj.T}
		size = int64(len(lobj))
		s.pushEvent(&Event{LTRIM, iden})
	}
	return
}

// register takes a control object avent and place it into the Wather list in
// keyspace event hub.
func (s *Store) register(inn *avent) {
	s.s.Lock()
	defer s.s.Unlock()
	r := s.s.counter
	s.s.counter = s.s.counter + 1
	s.s.list[r] = inn
}

// Watch provides interested party to monitor keyspace changes.
// A Watcher (caller of Watch function) provides a stopping condition, and gets
// a channel for future Event in keyspace.
// Stop a Watcher by closeing the stop channel
func (s *Store) Watch(stop <-chan struct{}) <-chan *Event {
	output := make(chan *Event, 8)
	go func() {
		defer close(output)
		incoming := make(chan *Event, 1)
		end := make(chan struct{})
		s.register(&avent{incoming, end})
		defer close(end)
		for yay := true; yay; {
			select {
			case <-stop:
				yay = false
			case ev := <-incoming:
				select {
				default:
					yay = false
				case output <- ev:
					continue
				}
			}
		}
	}()
	return output
}

// Key retrieves the full list of item key in Store.
func (s *Store) Key() (items []string) {
	s.RLock()
	defer s.RUnlock()
	items = make([]string, len(s.m.store))
	idx := 0
	for k, _ := range s.m.store {
		items[idx] = k
		idx++
	}
	return
}

// Keyexp retrieves the full list of item key in Store that has an expiration.
func (s *Store) Keyexp() (items []string) {
	s.RLock()
	defer s.RUnlock()
	items = make([]string, len(s.m.index))
	idx := 0
	for k, _ := range s.m.index {
		items[idx] = k
		idx++
	}
	return
}

// Iterate goes through the key set calles the provided handler with key and
// value
func (s *Store) IterateFunc(do func(string, interface{})) {
	s.RLock()
	defer s.RUnlock()
	for k, v := range s.m.store {
		do(k, v.X)
	}
}
<|endoftext|>


<commit_before>package transport

import (
	"crypto/rand"
	"crypto/tls"
	"encoding/xml"
	"fmt"
	"log"
	"net"

	"github.com/skriptble/nine/element"
	"github.com/skriptble/nine/element/stanza"
	"github.com/skriptble/nine/namespace"
	"github.com/skriptble/nine/stream"
)

// TCP is a stream transport that uses a TCP socket as described in RFC6120.
type TCP struct {
	net.Conn
	*xml.Decoder

	mode        stream.Mode
	tlsRequired bool
	conf        *tls.Config
	secure      bool
}

// NewTCP creates and returns a TCP stream.Transport
//
// tlsRequired will force tls upgrading of the stream before other features are
// negotiated.
//
// If conf is nil, the starttls feature will not be presented.
func NewTCP(c net.Conn, mode stream.Mode, conf *tls.Config, tlsRequired bool) stream.Transport {
	dec := xml.NewDecoder(c)
	return &TCP{Conn: c, Decoder: dec, mode: mode, conf: conf, tlsRequired: tlsRequired}
}

// WriteElement converts the element to bytes and writes to the underlying
// tcp connection. This method should generally be used for basic elements such
// as those used during SASL negotiation. WriteStanzas should be used when
// sending stanzas.
func (t *TCP) WriteElement(el element.Element) error {
	var b []byte
	b = el.WriteBytes()
	_, err := t.Write(b)
	return err
}

// WriteStanzas converts the stanza to bytes and writes them to the underlying
// tcp connection. This method should be used whenever stanzas are being used
// instead of transforming the stanza to an element and using WriteElement.
func (t *TCP) WriteStanza(st stanza.Stanza) error {
	el := st.TransformElement()
	return t.WriteElement(el)
}

// Next returns the next element from the stream. While most of the elements
// recieved from the stream are stanzas, this method is kept generic to allow
// handling stanzas and non-stanza elements such as those used during SASL
// neogitation.
//
// Since an element is the only valdi thing an XML stream can return, this is
// the only method to read data from a transport.
//
// This transport hides the starttls upgrade feature so if a starttls element
// would have been returned, the connection is upgraded instead.
func (t *TCP) Next() (el element.Element, err error) {
	defer func() {
		if el.Tag == "starttls" && !t.secure {
			el, err = t.startTLS()
		}
	}()
	var token xml.Token
	for {
		token, err = t.Token()
		if err != nil {
			return
		}

		switch elem := token.(type) {
		case xml.StartElement:
			return t.createElement(elem)
		case xml.EndElement:
			err = stream.ErrStreamClosed
			return
		}
	}
}

func (t *TCP) startTLS() (el element.Element, err error) {
	err = t.WriteElement(element.TLSProceed)
	if err != nil {
		return
	}
	tlsConn := tls.Server(t.Conn, t.conf)
	err = tlsConn.Handshake()
	if err != nil {
		return
	}
	conn := net.Conn(tlsConn)
	t.Conn = conn
	t.Decoder = xml.NewDecoder(conn)
	el = element.Element{}
	err = stream.ErrRequireRestart
	t.secure = true
	log.Println("Done upgrading connection")
	return
}

// Start starts or restarts the stream.
//
// In recieving mode, the transport will wait to recieve a stream header
// from the initiating entity, then sends its own header and the stream
// features. This transport will add the starttls feature under certain
// conditions.
func (t *TCP) Start(props stream.Properties) (stream.Properties, error) {
	if t.mode == stream.Initiating {
		if props.Header == (stream.Header{}) {
			return props, stream.ErrHeaderNotSet
		}
		b := props.Header.WriteBytes()
		_, err := t.Write(b)
		return props, err
	}

	// We're in recieving mode
	if props.Domain == "" {
		return props, stream.ErrDomainNotSet
	}
	var el element.Element
	var h stream.Header
	var err error

	el, err = t.Next()
	if err != nil {
		return props, err
	}

	h, err = stream.NewHeader(el)
	if err != nil {
		return props, err
	}

	h.ID = genStreamID()

	if h.To != props.Domain {
		h.To, h.From = h.From, props.Domain
		b := h.WriteBytes()
		t.Write(b)
		err = t.WriteElement(element.StreamError.HostUnknown)
		props.Status = stream.Closed
		return props, err
	}

	h.From, h.To = props.Domain, h.From
	if props.To != "" {
		h.To = props.To
	}

	props.Header = h

	b := props.Header.WriteBytes()
	_, err = t.Write(b)
	if err != nil {
		return props, err
	}

	ftrs := element.StreamFeatures
	for _, f := range props.Features {
		ftrs = ftrs.AddChild(f)
	}
	// Stream features
	if t.conf != nil && !t.secure {
		tlsFeature := element.StartTLS
		if t.tlsRequired {
			tlsFeature = tlsFeature.AddChild(element.Required)
		}
		// Overwrite any other features
		ftrs.Child = []element.Token{tlsFeature}
	}
	err = t.WriteElement(ftrs)
	return props, err
}

// createElement creates an element from the given xml.StartElement, populates
// its attributes and children and returns it.
func (t *TCP) createElement(start xml.StartElement) (el element.Element, err error) {
	var children []element.Token

	el = element.Element{
		Space: start.Name.Space,
		Tag:   start.Name.Local,
	}
	for _, attr := range start.Attr {
		el.Attr = append(
			el.Attr,
			element.Attr{
				Space: attr.Name.Space,
				Key:   attr.Name.Local,
				Value: attr.Value,
			},
		)
	}
	// If this is a stream start return only this element.
	if el.Tag == "stream" && el.Space == namespace.Stream {
		return
	}

	children, err = t.childElements()
	el.Child = children
	return
}

// childElements retrieves child tokens. This method should be called after
// createElement.
func (t *TCP) childElements() (children []element.Token, err error) {
	var token xml.Token
	var el element.Element
	for {
		token, err = t.Token()
		if err != nil {
			return
		}

		switch elem := token.(type) {
		case xml.StartElement:
			el, err = t.createElement(elem)
			if err != nil {
				return
			}
			children = append(children, el)
		case xml.EndElement:
			return
		case xml.CharData:
			data := string(elem)
			children = append(children, element.CharData{Data: data})
		}
	}
}

// genStreamID creates a new stream ID based on a uuid.
func genStreamID() string {
	id := make([]byte, 16)
	rand.Read(id)

	id[8] = (id[8] | 0x80) & 0xBF
	id[6] = (id[6] | 0x40) & 0x4F

	return fmt.Sprintf("ni%xne", id)
}
<commit_msg>Fixing typo in documentation.<commit_after>package transport

import (
	"crypto/rand"
	"crypto/tls"
	"encoding/xml"
	"fmt"
	"log"
	"net"

	"github.com/skriptble/nine/element"
	"github.com/skriptble/nine/element/stanza"
	"github.com/skriptble/nine/namespace"
	"github.com/skriptble/nine/stream"
)

// TCP is a stream transport that uses a TCP socket as described in RFC6120.
type TCP struct {
	net.Conn
	*xml.Decoder

	mode        stream.Mode
	tlsRequired bool
	conf        *tls.Config
	secure      bool
}

// NewTCP creates and returns a TCP stream.Transport
//
// tlsRequired will force tls upgrading of the stream before other features are
// negotiated.
//
// If conf is nil, the starttls feature will not be presented.
func NewTCP(c net.Conn, mode stream.Mode, conf *tls.Config, tlsRequired bool) stream.Transport {
	dec := xml.NewDecoder(c)
	return &TCP{Conn: c, Decoder: dec, mode: mode, conf: conf, tlsRequired: tlsRequired}
}

// WriteElement converts the element to bytes and writes to the underlying
// tcp connection. This method should generally be used for basic elements such
// as those used during SASL negotiation. WriteStanzas should be used when
// sending stanzas.
func (t *TCP) WriteElement(el element.Element) error {
	var b []byte
	b = el.WriteBytes()
	_, err := t.Write(b)
	return err
}

// WriteStanzas converts the stanza to bytes and writes them to the underlying
// tcp connection. This method should be used whenever stanzas are being used
// instead of transforming the stanza to an element and using WriteElement.
func (t *TCP) WriteStanza(st stanza.Stanza) error {
	el := st.TransformElement()
	return t.WriteElement(el)
}

// Next returns the next element from the stream. While most of the elements
// recieved from the stream are stanzas, this method is kept generic to allow
// handling stanzas and non-stanza elements such as those used during SASL
// neogitation.
//
// Since an element is the only valid thing an XML stream can return, this is
// the only method to read data from a transport.
//
// This transport hides the starttls upgrade feature so if a starttls element
// would have been returned, the connection is upgraded instead.
func (t *TCP) Next() (el element.Element, err error) {
	defer func() {
		if el.Tag == "starttls" && !t.secure {
			el, err = t.startTLS()
		}
	}()
	var token xml.Token
	for {
		token, err = t.Token()
		if err != nil {
			return
		}

		switch elem := token.(type) {
		case xml.StartElement:
			return t.createElement(elem)
		case xml.EndElement:
			err = stream.ErrStreamClosed
			return
		}
	}
}

func (t *TCP) startTLS() (el element.Element, err error) {
	err = t.WriteElement(element.TLSProceed)
	if err != nil {
		return
	}
	tlsConn := tls.Server(t.Conn, t.conf)
	err = tlsConn.Handshake()
	if err != nil {
		return
	}
	conn := net.Conn(tlsConn)
	t.Conn = conn
	t.Decoder = xml.NewDecoder(conn)
	el = element.Element{}
	err = stream.ErrRequireRestart
	t.secure = true
	log.Println("Done upgrading connection")
	return
}

// Start starts or restarts the stream.
//
// In recieving mode, the transport will wait to recieve a stream header
// from the initiating entity, then sends its own header and the stream
// features. This transport will add the starttls feature under certain
// conditions.
func (t *TCP) Start(props stream.Properties) (stream.Properties, error) {
	if t.mode == stream.Initiating {
		if props.Header == (stream.Header{}) {
			return props, stream.ErrHeaderNotSet
		}
		b := props.Header.WriteBytes()
		_, err := t.Write(b)
		return props, err
	}

	// We're in recieving mode
	if props.Domain == "" {
		return props, stream.ErrDomainNotSet
	}
	var el element.Element
	var h stream.Header
	var err error

	el, err = t.Next()
	if err != nil {
		return props, err
	}

	h, err = stream.NewHeader(el)
	if err != nil {
		return props, err
	}

	h.ID = genStreamID()

	if h.To != props.Domain {
		h.To, h.From = h.From, props.Domain
		b := h.WriteBytes()
		t.Write(b)
		err = t.WriteElement(element.StreamError.HostUnknown)
		props.Status = stream.Closed
		return props, err
	}

	h.From, h.To = props.Domain, h.From
	if props.To != "" {
		h.To = props.To
	}

	props.Header = h

	b := props.Header.WriteBytes()
	_, err = t.Write(b)
	if err != nil {
		return props, err
	}

	ftrs := element.StreamFeatures
	for _, f := range props.Features {
		ftrs = ftrs.AddChild(f)
	}
	// Stream features
	if t.conf != nil && !t.secure {
		tlsFeature := element.StartTLS
		if t.tlsRequired {
			tlsFeature = tlsFeature.AddChild(element.Required)
		}
		// Overwrite any other features
		ftrs.Child = []element.Token{tlsFeature}
	}
	err = t.WriteElement(ftrs)
	return props, err
}

// createElement creates an element from the given xml.StartElement, populates
// its attributes and children and returns it.
func (t *TCP) createElement(start xml.StartElement) (el element.Element, err error) {
	var children []element.Token

	el = element.Element{
		Space: start.Name.Space,
		Tag:   start.Name.Local,
	}
	for _, attr := range start.Attr {
		el.Attr = append(
			el.Attr,
			element.Attr{
				Space: attr.Name.Space,
				Key:   attr.Name.Local,
				Value: attr.Value,
			},
		)
	}
	// If this is a stream start return only this element.
	if el.Tag == "stream" && el.Space == namespace.Stream {
		return
	}

	children, err = t.childElements()
	el.Child = children
	return
}

// childElements retrieves child tokens. This method should be called after
// createElement.
func (t *TCP) childElements() (children []element.Token, err error) {
	var token xml.Token
	var el element.Element
	for {
		token, err = t.Token()
		if err != nil {
			return
		}

		switch elem := token.(type) {
		case xml.StartElement:
			el, err = t.createElement(elem)
			if err != nil {
				return
			}
			children = append(children, el)
		case xml.EndElement:
			return
		case xml.CharData:
			data := string(elem)
			children = append(children, element.CharData{Data: data})
		}
	}
}

// genStreamID creates a new stream ID based on a uuid.
func genStreamID() string {
	id := make([]byte, 16)
	rand.Read(id)

	id[8] = (id[8] | 0x80) & 0xBF
	id[6] = (id[6] | 0x40) & 0x4F

	return fmt.Sprintf("ni%xne", id)
}
<|endoftext|>


<commit_before>package main

import (
	"../libs/twodee"
)

type Player struct {
	*twodee.BaseEntity
	Health           float64
	eventSystem      *twodee.GameEventHandler
	moveToObserverId int
}

func (p *Player) MoveToObserver(e twodee.GETyper) {}

func (p *Player) Delete() {
	eventSystem.RemoveObserver(PlayerMove, moveToObserverId)
}

func NewPlayer(e *twodee.BaseEntity, eventSystem *twodee.GameEventHandler) (player *Player) {
	player = &Player{
		e,
		100.0,
		eventSystem,
		-1,
	}
	player.moveToObserverId = eventSystem.AddObserver(PlayerMove, player.MoveToObserver)
	return
}
<commit_msg>[Abe drops a glass]: Napoleon: We eat our mistakes here, monsieur Abe\!<commit_after>package main

import (
	"../libs/twodee"
)

type Player struct {
	*twodee.BaseEntity
	Health           float64
	eventSystem      *twodee.GameEventHandler
	moveToObserverId int
}

func (p *Player) MoveToObserver(e twodee.GETyper) {}

func (p *Player) Delete() {
	p.eventSystem.RemoveObserver(PlayerMove, p.moveToObserverId)
}

func NewPlayer(e *twodee.BaseEntity, eventSystem *twodee.GameEventHandler) (player *Player) {
	player = &Player{
		e,
		100.0,
		eventSystem,
		-1,
	}
	player.moveToObserverId = eventSystem.AddObserver(PlayerMove, player.MoveToObserver)
	return
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/rthornton128/goncurses"
	"math/rand"
	"os"
	"time"
)

const UP = 0
const DOWN = 1
const RIGHT = 2
const LEFT = 3

var changeProb float64
var randStart bool
var newColor bool
var dimmedColors bool
var waitTime time.Duration

func pipe(screenLock chan bool) {
	// Generate color
	color := int16(rand.Intn(14) + 1)

	// Variables for curDirection
	curDir := rand.Intn(3)
	var newDir, oldDir int

	// Window and coordinates
	win := goncurses.StdScr()
	maxY, maxX := win.MaxYX()
	var x, y int
	if randStart {
		x = rand.Intn(maxX)
		y = rand.Intn(maxY)
	} else {
		x = int(maxX / 2)
		y = int(maxY / 2)
	}

	for {
		// Store old curDirectiion
		oldDir = curDir
		if rand.Float64() > changeProb {
			// Get new curDirection
			newDir = rand.Intn(4)
			// Check if the curDirection isn't the reversed
			// old curDirection.
			if ((newDir + curDir) % 4) != 1 {
				curDir = newDir
			}
		}

		// Generate color and dimming attribute
		dimmed := false
		nColor := color
		if color > 7 {
			dimmed = dimmedColors
			nColor -= 7
		}

		// Get lock
		<-screenLock
		// Set color and attribute
		if dimmed {
			win.AttrOn(goncurses.A_DIM)
		} else {
			win.AttrOff(goncurses.A_DIM)
		}
		win.ColorOn(nColor)
		// Print ACS char and change coordinates
		if curDir == UP {
			if oldDir == LEFT {
				win.MoveAddChar(y, x, goncurses.ACS_LLCORNER)
			} else if oldDir == RIGHT {
				win.MoveAddChar(y, x, goncurses.ACS_LRCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_VLINE)
			}
			y--
		} else if curDir == DOWN {
			if oldDir == LEFT {
				win.MoveAddChar(y, x, goncurses.ACS_ULCORNER)
			} else if oldDir == RIGHT {
				win.MoveAddChar(y, x, goncurses.ACS_URCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_VLINE)
			}
			y++
		} else if curDir == RIGHT {
			if oldDir == UP {
				win.MoveAddChar(y, x, goncurses.ACS_ULCORNER)
			} else if oldDir == DOWN {
				win.MoveAddChar(y, x, goncurses.ACS_LLCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_HLINE)
			}
			x++
		} else if curDir == LEFT {
			if oldDir == UP {
				win.MoveAddChar(y, x, goncurses.ACS_URCORNER)
			} else if oldDir == DOWN {
				win.MoveAddChar(y, x, goncurses.ACS_LRCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_HLINE)
			}
			x--
		}
		// Give back lock
		screenLock <- true

		// Changing coordinates if leaving screen
		oob := true // Out of bounds
		if x > maxX {
			x = 0
		} else if y > maxY {
			y = 0
		} else if x < 0 {
			x = maxX
		} else if y < 0 {
			y = maxY
		} else {
			oob = false
		}
		// If the color needs to be changed and we went out of bounds
		// change the color
		if newColor && oob {
			color = int16(rand.Intn(14) + 1)
		}

		// Wait
		time.Sleep(waitTime)

	}

}

func main() {
	// Parse flags
	num_pipes := flag.Int("p", 1, "The `amount of pipes` to display")
	color := flag.Bool("C", false, "Disables color")
	DFlag := flag.Bool("D", false, "Use dimmed colors in addition to normal colors")
	NFlag := flag.Bool("N", false, "Changes the color of a pipe if it exits the screen")
	reset_lim := flag.Int("r", 2000, "Resets after the speciefied `amount of updates` (0 means no reset)")
	fps := flag.Int("f", 75, "Sets targeted `frames per second` that also dictate the moving speed")
	sVal := flag.Float64("s", 0.8, "`Probability` of NOT changing the curDirection (0.0 - 1.0)")
	RFlag := flag.Bool("R", false, "Start at random coordinates")
	flag.Parse()

	// Set variables
	changeProb = *sVal
	randStart = *RFlag
	newColor = *NFlag
	dimmedColors = *DFlag
	// Set FPS
	if *fps > 1000000 {
		waitTime = time.Duration(1) * time.Microsecond
	} else if *fps > 0 {
		waitTime = time.Duration(1000000 / *fps) * time.Microsecond
	} else {
		// 0 or negative FPS are impossible
		return
	}

	// Seeding RNG with current time
	rand.Seed(time.Now().Unix())

	// Init ncurses
	stdscr, err := goncurses.Init()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	defer goncurses.End()

	// More init
	if !*color {
		goncurses.StartColor()
	}
	goncurses.FlushInput()
	goncurses.Cursor(0)
	goncurses.Echo(false)
	goncurses.Raw(true)

	// Init colors
	goncurses.UseDefaultColors()
	goncurses.InitPair(1, goncurses.C_WHITE, -1)
	goncurses.InitPair(2, goncurses.C_GREEN, -1)
	goncurses.InitPair(3, goncurses.C_RED, -1)
	goncurses.InitPair(4, goncurses.C_YELLOW, -1)
	goncurses.InitPair(5, goncurses.C_BLUE, -1)
	goncurses.InitPair(6, goncurses.C_MAGENTA, -1)
	goncurses.InitPair(7, goncurses.C_CYAN, -1)

	// Set timeout and clear
	stdscr.AttrSet(goncurses.A_NORMAL)
	stdscr.Timeout(0)
	stdscr.Clear()
	stdscr.Refresh()

	// Creat channel for lock
	lock := make(chan bool, 1)
	lock <- true

	// Generate goroutines
	for i := 0; i < *num_pipes; i++ {
		go pipe(lock)
	}

	// Refresh loop (runs until a key was pressed)
	for i := 0; stdscr.GetChar() == 0; {
		// Wait
		time.Sleep(waitTime)

		// Only increment if reset limited is not 0
		if *reset_lim != 0 {
			i++
		}

		// Reset limit has been reached
		if i > *reset_lim {
			stdscr.Clear()
			i = 0
		}
	}

}
<commit_msg>Added -c colorscheme option<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/rthornton128/goncurses"
	"math/rand"
	"os"
	"time"
)

const UP = 0
const DOWN = 1
const RIGHT = 2
const LEFT = 3

var changeProb float64
var randStart bool
var newColor bool
var dimmedColors bool
var numColors int
var waitTime time.Duration

func pipe(screenLock chan bool) {
	// Generate color
	color := int16(rand.Intn(numColors * 2) + 1)

	// Variables for curDirection
	curDir := rand.Intn(3)
	var newDir, oldDir int

	// Window and coordinates
	win := goncurses.StdScr()
	maxY, maxX := win.MaxYX()
	var x, y int
	if randStart {
		x = rand.Intn(maxX)
		y = rand.Intn(maxY)
	} else {
		x = int(maxX / 2)
		y = int(maxY / 2)
	}

	for {
		// Store old direction
		oldDir = curDir
		if rand.Float64() > changeProb {
			// Get new direction
			newDir = rand.Intn(4)
			// Check if the direction isn't the reversed
			// old direction.
			if ((newDir + curDir) % 4) != 1 {
				curDir = newDir
			}
		}

		// Generate color and dimming attribute
		dimmed := false
		nColor := color
		if color > int16(numColors) {
			// Only dim if the flag has been set
			dimmed = dimmedColors
			// Subtract num of colors to get actual color
			nColor -= int16(numColors)
		}

		// Get lock
		<-screenLock
		// Set color and attribute
		if dimmed {
			win.AttrOn(goncurses.A_DIM)
		} else {
			win.AttrOff(goncurses.A_DIM)
		}
		win.ColorOn(nColor)
		// Print ACS char and change coordinates
		if curDir == UP {
			if oldDir == LEFT {
				win.MoveAddChar(y, x, goncurses.ACS_LLCORNER)
			} else if oldDir == RIGHT {
				win.MoveAddChar(y, x, goncurses.ACS_LRCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_VLINE)
			}
			y--
		} else if curDir == DOWN {
			if oldDir == LEFT {
				win.MoveAddChar(y, x, goncurses.ACS_ULCORNER)
			} else if oldDir == RIGHT {
				win.MoveAddChar(y, x, goncurses.ACS_URCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_VLINE)
			}
			y++
		} else if curDir == RIGHT {
			if oldDir == UP {
				win.MoveAddChar(y, x, goncurses.ACS_ULCORNER)
			} else if oldDir == DOWN {
				win.MoveAddChar(y, x, goncurses.ACS_LLCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_HLINE)
			}
			x++
		} else if curDir == LEFT {
			if oldDir == UP {
				win.MoveAddChar(y, x, goncurses.ACS_URCORNER)
			} else if oldDir == DOWN {
				win.MoveAddChar(y, x, goncurses.ACS_LRCORNER)
			} else {
				win.MoveAddChar(y, x, goncurses.ACS_HLINE)
			}
			x--
		}
		// Give back lock
		screenLock <- true

		// Changing coordinates if leaving screen
		oob := true // Out of bounds
		if x > maxX {
			x = 0
		} else if y > maxY {
			y = 0
		} else if x < 0 {
			x = maxX
		} else if y < 0 {
			y = maxY
		} else {
			oob = false
		}
		// If the color needs to be changed and we went out of bounds
		// change the color
		if newColor && oob {
			color = int16(rand.Intn(numColors * 2) + 1)
		}

		// Wait
		time.Sleep(waitTime)

	}

}

func setColorScheme(scheme int) int {
	// Try to use the default background
	var background int16
	err := goncurses.UseDefaultColors()
	if err != nil {
		background = goncurses.C_BLACK
	} else {
		background = -1
	}
	// Init pairs according to scheme
	switch scheme {
	default:
		goncurses.InitPair(1, goncurses.C_WHITE, background)
		goncurses.InitPair(2, goncurses.C_GREEN, background)
		goncurses.InitPair(3, goncurses.C_RED, background)
		goncurses.InitPair(4, goncurses.C_YELLOW, background)
		goncurses.InitPair(5, goncurses.C_BLUE, background)
		goncurses.InitPair(6, goncurses.C_MAGENTA, background)
		goncurses.InitPair(7, goncurses.C_CYAN, background)
		return 7
	case 1:
		goncurses.InitPair(1, goncurses.C_WHITE, background)
		goncurses.InitPair(2, goncurses.C_BLUE, background)
		goncurses.InitPair(3, goncurses.C_CYAN, background)
		return 3
	case 2:
		goncurses.InitPair(1, goncurses.C_RED, background)
		goncurses.InitPair(2, goncurses.C_YELLOW, background)
		goncurses.InitPair(3, goncurses.C_GREEN, background)
		return 3
	case 3:
		goncurses.InitPair(1, goncurses.C_WHITE, background)
		goncurses.InitPair(2, goncurses.C_BLUE, background)
		goncurses.InitPair(3, goncurses.C_RED, background)
		return 3
	case 4:
		goncurses.InitPair(1, goncurses.C_RED, background)
		goncurses.InitPair(2, goncurses.C_GREEN, background)
		goncurses.InitPair(3, goncurses.C_BLUE, background)
		return 3
	case 5:
		goncurses.InitPair(1, goncurses.C_WHITE, background)
		goncurses.InitPair(2, goncurses.C_RED, background)
		return 2
	case 6:
		goncurses.InitPair(1, goncurses.C_WHITE, background)
		goncurses.InitPair(2, goncurses.C_BLUE, background)
		return 2
	case 7:
		goncurses.InitPair(1, goncurses.C_WHITE, background)
		goncurses.InitPair(2, goncurses.C_GREEN, background)
		return 2
	}
}

func main() {
	// Parse flags
	numPipes := flag.Int("p", 1, "The `amount of pipes` to display")
	color := flag.Bool("C", false, "Disables color")
	DFlag := flag.Bool("D", false, "Use dimmed colors in addition to normal colors")
	NFlag := flag.Bool("N", false, "Changes the color of a pipe if it exits the screen")
	resetLim := flag.Int("r", 2000, "Resets after the speciefied `amount of updates` (0 means no reset)")
	fps := flag.Int("f", 75, "Sets targeted `frames per second` that also dictate the moving speed")
	colorScheme := flag.Int("c", 0, "Sets the `colorscheme` (0-7)")
	sVal := flag.Float64("s", 0.8, "`Probability` of NOT changing the curDirection (0.0-1.0)")
	RFlag := flag.Bool("R", false, "Start at random coordinates")
	flag.Parse()

	// Set variables
	changeProb = *sVal
	randStart = *RFlag
	newColor = *NFlag
	dimmedColors = *DFlag
	// Set FPS
	if *fps > 1000000 {
		waitTime = time.Duration(1) * time.Microsecond
	} else if *fps > 0 {
		waitTime = time.Duration(1000000 / *fps) * time.Microsecond
	} else {
		// 0 or negative FPS are impossible
		return
	}

	// Seeding RNG with current time
	rand.Seed(time.Now().Unix())

	// Init ncurses
	stdscr, err := goncurses.Init()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	defer goncurses.End()

	// More init
	if !*color {
		goncurses.StartColor()
	}
	goncurses.FlushInput()
	goncurses.Cursor(0)
	goncurses.Echo(false)
	goncurses.Raw(true)

	// Init color pairs and number of colors
	numColors = setColorScheme(*colorScheme)

	// Set timeout, normal attribute and clear screen
	stdscr.AttrSet(goncurses.A_NORMAL)
	stdscr.Timeout(0)
	stdscr.Clear()
	stdscr.Refresh()

	// Creat channel for lock
	lock := make(chan bool, 1)
	lock <- true

	// Generate goroutines
	for i := 0; i < *numPipes; i++ {
		go pipe(lock)
	}

	// Refresh loop (runs until a key was pressed)
	for i := 0; stdscr.GetChar() == 0; {
		// Wait
		time.Sleep(waitTime)

		// Only increment if reset limited is not 0
		if *resetLim != 0 {
			i++
		}

		// Reset limit has been reached
		if i > *resetLim {
			stdscr.Clear()
			i = 0
		}
	}

}
<|endoftext|>