<commit_before>/*
Copyright 2019 The Knative Authors

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 logstream

import (
	"bufio"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	"golang.org/x/sync/errgroup"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"

	"knative.dev/pkg/ptr"
	"knative.dev/pkg/test"
	"knative.dev/pkg/test/helpers"
)

type kubelogs struct {
	namespace string
	kc        *test.KubeClient

	once sync.Once
	m    sync.RWMutex
	keys map[string]logger
	err  error
}

type logger func(string, ...interface{})

var _ streamer = (*kubelogs)(nil)

// timeFormat defines a simple timestamp with millisecond granularity
const timeFormat = "15:04:05.000"

func (k *kubelogs) startForPod(eg *errgroup.Group, pod *corev1.Pod) {
	// Grab data from all containers in the pods.  We need this in case
	// an envoy sidecar is injected for mesh installs.  This should be
	// equivalent to --all-containers.
	for _, container := range pod.Spec.Containers {
		// Required for capture below.
		psn, pn, cn := pod.Namespace, pod.Name, container.Name

		handleLine := k.handleLine
		if cn == "chaosduck" {
			// Specialcase logs from chaosduck to be able to easily see when pods
			// have been killed throughout all tests.
			handleLine = k.handleGenericLine
		}

		eg.Go(func() error {
			options := &corev1.PodLogOptions{
				Container: cn,
				// Follow directs the api server to continuously stream logs back.
				Follow: true,
				// Only return new logs (this value is being used for "epsilon").
				SinceSeconds: ptr.Int64(1),
			}

			req := k.kc.Kube.CoreV1().Pods(psn).GetLogs(pn, options)
			stream, err := req.Stream()
			if err != nil {
				return err
			}
			defer stream.Close()
			// Read this container's stream.
			scanner := bufio.NewScanner(stream)
			for scanner.Scan() {
				handleLine(scanner.Bytes(), pn)
			}
			// Pods get killed with chaos duck, so logs might end
			// before the test does. So don't report an error here.
			return nil
		})
	}
}

func podIsReady(p *corev1.Pod) bool {
	if p.Status.Phase == corev1.PodRunning && p.DeletionTimestamp == nil {
		for _, cond := range p.Status.Conditions {
			if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue {
				return true
			}
		}
	}
	return false
}

func (k *kubelogs) watchPods(t test.TLegacy) {
	wi, err := k.kc.Kube.CoreV1().Pods(k.namespace).Watch(metav1.ListOptions{})
	if err != nil {
		t.Error("Logstream knative pod watch failed, logs might be missing", "error", err)
		return
	}
	eg := errgroup.Group{}
	eg.Go(func() error {
		watchedPods := sets.NewString()
		for ev := range wi.ResultChan() {
			p := ev.Object.(*corev1.Pod)
			switch ev.Type {
			case watch.Deleted:
				watchedPods.Delete(p.Name)
			case watch.Added, watch.Modified:
				if watchedPods.Has(p.Name) {
					continue
				}
				if podIsReady(p) {
					watchedPods.Insert(p.Name)
					k.startForPod(&eg, p)
					continue
				}
			}
		}
		return nil
	})
	// Monitor the error group in the background and surface an error on the kubelogs
	// in case anything had an active stream open.
	go func() {
		if err := eg.Wait(); err != nil {
			k.m.Lock()
			defer k.m.Unlock()
			k.err = err
		}
	}()
}

func (k *kubelogs) init(t test.TLegacy) {
	k.keys = make(map[string]logger, 1)

	kc, err := test.NewKubeClient(test.Flags.Kubeconfig, test.Flags.Cluster)
	if err != nil {
		t.Error("Error loading client config", "error", err)
		return
	}
	k.kc = kc

	// watchPods will start logging for existing pods as well.
	k.watchPods(t)
}

func (k *kubelogs) handleLine(l []byte, pod string) {
	// This holds the standard structure of our logs.
	var line struct {
		Level      string    `json:"level"`
		Timestamp  time.Time `json:"ts"`
		Controller string    `json:"knative.dev/controller"`
		Caller     string    `json:"caller"`
		Key        string    `json:"knative.dev/key"`
		Message    string    `json:"msg"`
		Error      string    `json:"error"`

		// TODO(mattmoor): Parse out more context.
	}
	if err := json.Unmarshal(l, &line); err != nil {
		// Ignore malformed lines.
		return
	}
	if line.Key == "" {
		return
	}

	k.m.RLock()
	defer k.m.RUnlock()

	for name, logf := range k.keys {
		// TODO(mattmoor): Do a slightly smarter match.
		if !strings.Contains(line.Key, name) {
			continue
		}

		// We also get logs not from controllers (activator, autoscaler).
		// So replace controller string in them with their callsite.
		site := line.Controller
		if site == "" {
			site = line.Caller
		}
		// E 15:04:05.000 webhook-699b7b668d-9smk2 [route-controller] [default/testroute-xyz] this is my message
		msg := fmt.Sprintf("%s %s %s [%s] [%s] %s",
			strings.ToUpper(string(line.Level[0])),
			line.Timestamp.Format(timeFormat),
			pod,
			site,
			line.Key,
			line.Message)

		if line.Error != "" {
			msg += " err=" + line.Error
		}

		logf(msg)
	}
}

// handleGenericLine prints the given logline to all active tests as it cannot be parsed
// and/or doesn't contain any correlation data (like the chaosduck for example).
func (k *kubelogs) handleGenericLine(l []byte, pod string) {
	k.m.RLock()
	defer k.m.RUnlock()

	for _, logf := range k.keys {
		// I 15:04:05.000 webhook-699b7b668d-9smk2 this is my message
		logf("I %s %s %s", time.Now().Format(timeFormat), pod, string(l))
	}
}

// Start implements streamer.
func (k *kubelogs) Start(t test.TLegacy) Canceler {
	k.once.Do(func() { k.init(t) })

	name := helpers.ObjectPrefixForTest(t)

	// Register a key
	k.m.Lock()
	defer k.m.Unlock()
	k.keys[name] = t.Logf

	// Return a function that unregisters that key.
	return func() {
		k.m.Lock()
		defer k.m.Unlock()
		delete(k.keys, name)

		if k.err != nil {
			t.Error("Error during logstream", "error", k.err)
		}
	}
}
<commit_msg>Remove ErrGroup from kubelogs altogether (#1588)<commit_after>/*
Copyright 2019 The Knative Authors

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 logstream

import (
	"bufio"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"time"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"

	"knative.dev/pkg/ptr"
	"knative.dev/pkg/test"
	"knative.dev/pkg/test/helpers"
)

type kubelogs struct {
	namespace string
	kc        *test.KubeClient

	once sync.Once
	m    sync.RWMutex
	keys map[string]logger
	err  error
}

type logger func(string, ...interface{})

var _ streamer = (*kubelogs)(nil)

// timeFormat defines a simple timestamp with millisecond granularity
const timeFormat = "15:04:05.000"

func (k *kubelogs) startForPod(pod *corev1.Pod) {
	// Grab data from all containers in the pods.  We need this in case
	// an envoy sidecar is injected for mesh installs.  This should be
	// equivalent to --all-containers.
	for _, container := range pod.Spec.Containers {
		// Required for capture below.
		psn, pn, cn := pod.Namespace, pod.Name, container.Name

		handleLine := k.handleLine
		if cn == "chaosduck" {
			// Specialcase logs from chaosduck to be able to easily see when pods
			// have been killed throughout all tests.
			handleLine = k.handleGenericLine
		}

		go func() {
			options := &corev1.PodLogOptions{
				Container: cn,
				// Follow directs the API server to continuously stream logs back.
				Follow: true,
				// Only return new logs (this value is being used for "epsilon").
				SinceSeconds: ptr.Int64(1),
			}

			req := k.kc.Kube.CoreV1().Pods(psn).GetLogs(pn, options)
			stream, err := req.Stream()
			if err != nil {
				func() {
					k.m.Lock()
					defer k.m.Unlock()
					k.err = err
				}()
			}
			defer stream.Close()
			// Read this container's stream.
			scanner := bufio.NewScanner(stream)
			for scanner.Scan() {
				handleLine(scanner.Bytes(), pn)
			}
			// Pods get killed with chaos duck, so logs might end
			// before the test does. So don't report an error here.
		}()
	}
}

func podIsReady(p *corev1.Pod) bool {
	if p.Status.Phase == corev1.PodRunning && p.DeletionTimestamp == nil {
		for _, cond := range p.Status.Conditions {
			if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue {
				return true
			}
		}
	}
	return false
}

func (k *kubelogs) watchPods(t test.TLegacy) {
	wi, err := k.kc.Kube.CoreV1().Pods(k.namespace).Watch(metav1.ListOptions{})
	if err != nil {
		t.Error("Logstream knative pod watch failed, logs might be missing", "error", err)
		return
	}
	go func() error {
		watchedPods := sets.NewString()
		for ev := range wi.ResultChan() {
			p := ev.Object.(*corev1.Pod)
			switch ev.Type {
			case watch.Deleted:
				watchedPods.Delete(p.Name)
			case watch.Added, watch.Modified:
				if watchedPods.Has(p.Name) {
					continue
				}
				if podIsReady(p) {
					watchedPods.Insert(p.Name)
					k.startForPod(p)
					continue
				}
			}
		}
		return nil
	}()
}

func (k *kubelogs) init(t test.TLegacy) {
	k.keys = make(map[string]logger, 1)

	kc, err := test.NewKubeClient(test.Flags.Kubeconfig, test.Flags.Cluster)
	if err != nil {
		t.Error("Error loading client config", "error", err)
		return
	}
	k.kc = kc

	// watchPods will start logging for existing pods as well.
	k.watchPods(t)
}

func (k *kubelogs) handleLine(l []byte, pod string) {
	// This holds the standard structure of our logs.
	var line struct {
		Level      string    `json:"level"`
		Timestamp  time.Time `json:"ts"`
		Controller string    `json:"knative.dev/controller"`
		Caller     string    `json:"caller"`
		Key        string    `json:"knative.dev/key"`
		Message    string    `json:"msg"`
		Error      string    `json:"error"`

		// TODO(mattmoor): Parse out more context.
	}
	if err := json.Unmarshal(l, &line); err != nil {
		// Ignore malformed lines.
		return
	}
	if line.Key == "" {
		return
	}

	k.m.RLock()
	defer k.m.RUnlock()

	for name, logf := range k.keys {
		// TODO(mattmoor): Do a slightly smarter match.
		if !strings.Contains(line.Key, name) {
			continue
		}

		// We also get logs not from controllers (activator, autoscaler).
		// So replace controller string in them with their callsite.
		site := line.Controller
		if site == "" {
			site = line.Caller
		}
		// E 15:04:05.000 webhook-699b7b668d-9smk2 [route-controller] [default/testroute-xyz] this is my message
		msg := fmt.Sprintf("%s %s %s [%s] [%s] %s",
			strings.ToUpper(string(line.Level[0])),
			line.Timestamp.Format(timeFormat),
			pod,
			site,
			line.Key,
			line.Message)

		if line.Error != "" {
			msg += " err=" + line.Error
		}

		logf(msg)
	}
}

// handleGenericLine prints the given logline to all active tests as it cannot be parsed
// and/or doesn't contain any correlation data (like the chaosduck for example).
func (k *kubelogs) handleGenericLine(l []byte, pod string) {
	k.m.RLock()
	defer k.m.RUnlock()

	for _, logf := range k.keys {
		// I 15:04:05.000 webhook-699b7b668d-9smk2 this is my message
		logf("I %s %s %s", time.Now().Format(timeFormat), pod, string(l))
	}
}

// Start implements streamer.
func (k *kubelogs) Start(t test.TLegacy) Canceler {
	k.once.Do(func() { k.init(t) })

	name := helpers.ObjectPrefixForTest(t)

	// Register a key
	k.m.Lock()
	defer k.m.Unlock()
	k.keys[name] = t.Logf

	// Return a function that unregisters that key.
	return func() {
		k.m.Lock()
		defer k.m.Unlock()
		delete(k.keys, name)

		if k.err != nil {
			t.Error("Error during logstream", "error", k.err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "log"
    "net/http"
    "sort"
    "strconv"
    "strings"
    "math"
)

// Constants with html code for our web page
const (
    pageTop = `
        <!DOCTYPE html>

            <title>Statistics</title>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">

            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

Statistics

Computes basic statistics for a given list of numbers
`
    form = `
                <form role="form" action="/" method="POST">

                        Numbers (comma or space-separated):
                        <input type="text" class="form-control" id="numbers" name="numbers" value="" placeholder="(e.g. 1 2 3)" />

                    <button type="submit" class="btn btn-success" >Calculate</button>
                </form>`
    pageBottom = `

        `
    anError = `<br /><p class="text-danger">%s</p>`
)

// Struct holding the user's numbers and the resulting statistics
type statistics struct {
    numbers []float64
    mean    float64
    median  float64
    σ  float64
}

func main() {
    // Setup the web server handling the requests
    http.HandleFunc("/", homePage)
    if err := http.ListenAndServe("0.0.0.0:8080", nil); err != nil {
        log.Fatal("failed to start server", err)
    }
}

// Handling the call to the home page; i.e. handling everything because there
// is no other page!
func homePage(writer http.ResponseWriter, request *http.Request) {
    err := request.ParseForm() // Must be called before writing the response
    fmt.Fprint(writer, pageTop, form)

    if err != nil {
        fmt.Fprintf(writer, anError, err)
    } else {
        if numbers, message, ok := processRequest(request); ok {
            stats := getStats(numbers)
            fmt.Fprint(writer, formatStats(stats))
        } else if message != "" {
            fmt.Fprintf(writer, anError, message)
        }
    }

    fmt.Fprint(writer, pageBottom)
}

// Process the http request
func processRequest(request *http.Request) ([]float64, string, bool) {
    var numbers []float64

    if slice, found := request.Form["numbers"]; found && len(slice) > 0 {
        // There has been entered some text in the "numbers" input box
        // Replace commas with space
        text := strings.Replace(slice[0], ",", " ", -1)

        // Iterate over the white space separated text fields
        for _, field := range strings.Fields(text) {
            if x, err := strconv.ParseFloat(field, 64); err != nil {
                // Could not convert text to float64
                return numbers, "'" + field + "' is invalid", false
            } else {
                // We have a valid float64 number
                numbers = append(numbers, x)
            }
        }
    }

    if len(numbers) == 0 {
        // No data, page has just been called and shown
        return numbers, "", false
    }

    // A slice of float64 numbers
    return numbers, "", true
}

// Calculate the statistics
func getStats(numbers []float64) (stats statistics) {
    stats.numbers = numbers
    // The median function expects the numbers to be sorted
    sort.Float64s(stats.numbers)
    stats.mean = sum(numbers) / float64(len(numbers))
    stats.median = median(stats.numbers)
    stats.σ = σ(stats.numbers)

    return stats
}

// Calculate the sum of a slice of float64 numbers
func sum(numbers []float64) (total float64) {
    for _, x := range numbers {
        total += x
    }

    return total
}

// Calculate the median of a sorted slice of float64 numbers
func median(numbers []float64) float64 {
    middle := len(numbers) / 2
    result := numbers[middle]

    // Correct median for multiple of 2 length
    if len(numbers) % 2 == 0 {
        result = (result + numbers[middle-1]) / 2
    }

    return result
}

// Calculate the standard deviation of float64 numbers
func σ(numbers []float64) (σ float64) {

    n := len(numbers)

    if n < 2 {
        return σ
    }

    mean := sum(numbers) / float64(n)
    var s float64

    for _, x := range numbers {
        s += (x - mean) * (x - mean)
    }

    σ = math.Sqrt(s/float64(n-1))

    return σ
}

// Format the statistics html output
func formatStats(stats statistics) string {
    return fmt.Sprintf(`

Results:

Numbers	%v

Count	%d

Mean	%f

Median	%f

σ	%f

`,
        stats.numbers, len(stats.numbers), stats.mean, stats.median, stats.σ)
}<commit_msg>Added mode to output<commit_after>package main

import (
    "fmt"
    "log"
    "net/http"
    "sort"
    "strconv"
    "strings"
    "math"
)

// Constants with html code for our web page
const (
    pageTop = `
        <!DOCTYPE html>

            <title>Statistics</title>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">

            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">

Statistics

Computes basic statistics for a given list of numbers
`
    form = `
                <form role="form" action="/" method="POST">

                        Numbers (comma or space-separated):
                        <input type="text" class="form-control" id="numbers" name="numbers" value="" placeholder="(e.g. 1 2 3)" />

                    <button type="submit" class="btn btn-success" >Calculate</button>
                </form>`
    pageBottom = `

        `
    anError = `<br /><p class="text-danger">%s</p>`
)

// Struct holding the user's numbers and the resulting statistics
type statistics struct {
    numbers []float64
    mean    float64
    median  float64
    mode    []float64
    σ       float64
}

func main() {
    // Setup the web server handling the requests
    http.HandleFunc("/", homePage)
    if err := http.ListenAndServe("0.0.0.0:8080", nil); err != nil {
        log.Fatal("failed to start server", err)
    }
}

// Handling the call to the home page; i.e. handling everything because there
// is no other page!
func homePage(writer http.ResponseWriter, request *http.Request) {
    err := request.ParseForm() // Must be called before writing the response
    fmt.Fprint(writer, pageTop, form)

    if err != nil {
        fmt.Fprintf(writer, anError, err)
    } else {
        if numbers, message, ok := processRequest(request); ok {
            stats := getStats(numbers)
            fmt.Fprint(writer, formatStats(stats))
        } else if message != "" {
            fmt.Fprintf(writer, anError, message)
        }
    }

    fmt.Fprint(writer, pageBottom)
}

// Process the http request
func processRequest(request *http.Request) ([]float64, string, bool) {
    var numbers []float64

    if slice, found := request.Form["numbers"]; found && len(slice) > 0 {
        // There has been entered some text in the "numbers" input box
        // Replace commas with space
        text := strings.Replace(slice[0], ",", " ", -1)

        // Iterate over the white space separated text fields
        for _, field := range strings.Fields(text) {
            if x, err := strconv.ParseFloat(field, 64); err != nil {
                // Could not convert text to float64
                return numbers, "'" + field + "' is invalid", false
            } else {
                // We have a valid float64 number
                numbers = append(numbers, x)
            }
        }
    }

    if len(numbers) == 0 {
        // No data, page has just been called and shown
        return numbers, "", false
    }

    // A slice of float64 numbers
    return numbers, "", true
}

// Calculate the statistics
func getStats(numbers []float64) (stats statistics) {
    stats.numbers = numbers
    // The median function expects the numbers to be sorted
    sort.Float64s(stats.numbers)
    stats.mean = sum(numbers) / float64(len(numbers))
    stats.median = median(stats.numbers)
    stats.mode = mode(stats.numbers)
    stats.σ = σ(stats.numbers)

    return stats
}

// Calculate the sum of a slice of float64 numbers
func sum(numbers []float64) (total float64) {
    for _, x := range numbers {
        total += x
    }

    return total
}

// Calculate the median of a sorted slice of float64 numbers
func median(numbers []float64) float64 {
    middle := len(numbers) / 2
    result := numbers[middle]

    // Correct median for multiple of 2 length
    if len(numbers) % 2 == 0 {
        result = (result + numbers[middle-1]) / 2
    }

    return result
}

// Calculate the mode of float64 numbers
func mode(numbers []float64) (mode []float64) {
    // count the occurrences of the numbers
    occurrences := make(map[float64]int)

    for _, x := range numbers {
        counter := occurrences[x]
        occurrences[x] = counter + 1
    }

    // collect the numbers with maximum occurrences
    max := 0

    for x, counter := range occurrences {
        if counter > max {
            max = counter
            mode = nil
            mode = append(mode, x)
        } else if counter == max {
            mode = append(mode, x)
        }
    }

    // if all numbers have equal count, then return nil
    if len(mode) == len(occurrences) {
        mode = nil
    }

    return mode
}

// Calculate the standard deviation of float64 numbers
func σ(numbers []float64) (σ float64) {

    n := len(numbers)

    if n < 2 {
        return σ
    }

    mean := sum(numbers) / float64(n)
    var s float64

    for _, x := range numbers {
        s += (x - mean) * (x - mean)
    }

    σ = math.Sqrt(s/float64(n-1))

    return σ
}

// Format the statistics html output
func formatStats(stats statistics) string {
    return fmt.Sprintf(`

Results:

Numbers	%v

Count	%d

Mean	%f

Median	%f

Mode	%v

σ	%f

`,
        stats.numbers, len(stats.numbers),
        stats.mean, stats.median, stats.mode, stats.σ)
}<|endoftext|>


<commit_before>package influxdb_test

import (
	"testing"

	"github.com/influxdb/influxdb"
)

func TestStats_SetAndGet(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Set("a", 100)
	if s.Get("a") != 100 {
		t.Fatalf("stats set failed, expected 100, got %d", s.Get("a"))
	}
}

func TestStats_Add(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Add("a", 200)
	if s.Get("a") != 200 {
		t.Fatalf("stats set failed, expected 200, got %d", s.Get("a"))
	}
}

func TestStats_Inc(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Set("a", 100)
	s.Inc("a")
	if s.Get("a") != 101 {
		t.Fatalf("stats Inc failed, expected 101, got %d", s.Get("a"))
	}

	s.Inc("b")
	if s.Get("b") != 1 {
		t.Fatalf("stats Inc failed, expected 1, got %d", s.Get("b"))
	}
}

func TestStats_AddNegative(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Add("a", -200)
	if s.Get("a") != -200 {
		t.Fatalf("stats set failed, expected -200, got %d", s.Get("a"))
	}
}

func TestStats_SetAndAdd(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Set("a", 100)
	s.Add("a", 200)
	if s.Get("a") != 300 {
		t.Fatalf("stats set failed, expected 300, got %d", s.Get("a"))
	}
}

func TestStats_Diff(t *testing.T) {
	foo := influxdb.NewStats("server")
	bar := influxdb.NewStats("server")

	foo.Set("a", 100)
	foo.Set("b", 600)
	bar.Set("a", 450)
	bar.Set("b", 525)

	qux := bar.Diff(foo)
	if qux.Name() != "server" {
		t.Fatalf("stats diff has unexpected name: %s", qux.Name())
	}
	if qux.Get("a") != 350 || qux.Get("b") != -75 {
		t.Fatalf("stats diff returned unexpected result: %v", qux)
	}
}

func TestStats_Snapshot(t *testing.T) {
	foo := influxdb.NewStats("server")
	foo.Set("a", 100)
	foo.Set("b", 600)

	bar := foo.Snapshot()
	if bar.Name() != "server" || bar.Get("a") != 100 || bar.Get("b") != 600 {
		t.Fatalf("stats snapshot returned unexpected result: %s", bar)
	}
}
<commit_msg>fix vet error<commit_after>package influxdb_test

import (
	"testing"

	"github.com/influxdb/influxdb"
)

func TestStats_SetAndGet(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Set("a", 100)
	if s.Get("a") != 100 {
		t.Fatalf("stats set failed, expected 100, got %d", s.Get("a"))
	}
}

func TestStats_Add(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Add("a", 200)
	if s.Get("a") != 200 {
		t.Fatalf("stats set failed, expected 200, got %d", s.Get("a"))
	}
}

func TestStats_Inc(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Set("a", 100)
	s.Inc("a")
	if s.Get("a") != 101 {
		t.Fatalf("stats Inc failed, expected 101, got %d", s.Get("a"))
	}

	s.Inc("b")
	if s.Get("b") != 1 {
		t.Fatalf("stats Inc failed, expected 1, got %d", s.Get("b"))
	}
}

func TestStats_AddNegative(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Add("a", -200)
	if s.Get("a") != -200 {
		t.Fatalf("stats set failed, expected -200, got %d", s.Get("a"))
	}
}

func TestStats_SetAndAdd(t *testing.T) {
	s := influxdb.NewStats("foo")

	s.Set("a", 100)
	s.Add("a", 200)
	if s.Get("a") != 300 {
		t.Fatalf("stats set failed, expected 300, got %d", s.Get("a"))
	}
}

func TestStats_Diff(t *testing.T) {
	foo := influxdb.NewStats("server")
	bar := influxdb.NewStats("server")

	foo.Set("a", 100)
	foo.Set("b", 600)
	bar.Set("a", 450)
	bar.Set("b", 525)

	qux := bar.Diff(foo)
	if qux.Name() != "server" {
		t.Fatalf("stats diff has unexpected name: %s", qux.Name())
	}
	if qux.Get("a") != 350 || qux.Get("b") != -75 {
		t.Fatalf("stats diff returned unexpected result: %v", qux)
	}
}

func TestStats_Snapshot(t *testing.T) {
	foo := influxdb.NewStats("server")
	foo.Set("a", 100)
	foo.Set("b", 600)

	bar := foo.Snapshot()
	if bar.Name() != "server" || bar.Get("a") != 100 || bar.Get("b") != 600 {
		t.Fatalf("stats snapshot returned unexpected result: %#v", bar)
	}
}
<|endoftext|>


<commit_before>package oauth

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"fmt"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"
	"github.com/openshift/client-go/user/clientset/versioned"
	"k8s.io/apimachinery/pkg/api/errors"

	"k8s.io/client-go/rest"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	oauthv1 "github.com/openshift/api/oauth/v1"
	userv1 "github.com/openshift/api/user/v1"
	exutil "github.com/openshift/origin/test/extended/util"
)

var _ = g.Describe("[sig-auth][Feature:OAuthServer] OAuth Authenticator", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("oauth-access-token-e2e-test")
	ctx := context.Background()

	g.It(fmt.Sprintf("accepts classic non-prefixed access tokens"), func() {
		user, err := oc.AdminUserClient().UserV1().Users().Create(ctx, &userv1.User{
			TypeMeta: metav1.TypeMeta{},
			ObjectMeta: metav1.ObjectMeta{
				Name: "oauth-access-token-e2e-test-user-classical",
			},
			FullName: "test user",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(userv1.GroupVersion.WithResource("users"), user)

		g.By("creating a classic oauth access token")
		token := "0123456789012345678900123456789001234567890123"
		classicTokenObject, err := oc.AdminOauthClient().OauthV1().OAuthAccessTokens().Create(ctx, &oauthv1.OAuthAccessToken{
			ObjectMeta: metav1.ObjectMeta{
				Name: token,
			},
			UserName:    user.Name,
			UserUID:     string(user.UID),
			ClientName:  "openshift-challenging-client",
			Scopes:      []string{"user:info"},
			RedirectURI: "https://127.0.0.1:12000/oauth/token/implicit",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(oauthv1.GroupVersion.WithResource("oauthaccesstokens"), classicTokenObject)

		g.By("authenticating using the classic access token as bearer token")
		gotUser, err := whoamiWithToken(token, oc)
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(gotUser.Name).To(o.Equal(user.Name))

		g.By("not-authenticating using a sha256 prefixed access token as bearer token")
		_, err = whoamiWithToken("sha256:"+token, oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())
	})

	g.It(fmt.Sprintf("accepts sha256 access tokens"), func() {
		user, err := oc.AdminUserClient().UserV1().Users().Create(ctx, &userv1.User{
			TypeMeta: metav1.TypeMeta{},
			ObjectMeta: metav1.ObjectMeta{
				Name: "oauth-access-token-e2e-test-user-sha256",
			},
			FullName: "test user",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(userv1.GroupVersion.WithResource("users"), user)

		g.By("creating a classic oauth access token")
		token := "0123456789012345678900123456789001234567890123"
		bs := sha256.Sum256([]byte(token))
		hash := base64.RawURLEncoding.EncodeToString(bs[:])
		classicTokenObject, err := oc.AdminOauthClient().OauthV1().OAuthAccessTokens().Create(ctx, &oauthv1.OAuthAccessToken{
			ObjectMeta: metav1.ObjectMeta{
				Name: "sha256:" + hash[0:],
			},
			UserName:    user.Name,
			UserUID:     string(user.UID),
			ClientName:  "openshift-challenging-client",
			Scopes:      []string{"user:info"},
			RedirectURI: "https://127.0.0.1:12000/oauth/token/implicit",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(oauthv1.GroupVersion.WithResource("oauthaccesstokens"), classicTokenObject)

		g.By("authenticating using the sha256 prefixed access token as bearer token")
		gotUser, err := whoamiWithToken("sha256:"+token, oc)
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(gotUser.Name).To(o.Equal(user.Name))

		g.By("not-authenticating using a sha256 prefixed hash as bearer token")
		_, err = whoamiWithToken("sha256:"+hash[0:], oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())

		g.By("not-authenticating using a non-prefixed token as bearer token")
		_, err = whoamiWithToken(token, oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())

		g.By("not-authenticating using a non-prefixed hash as bearer token")
		_, err = whoamiWithToken(hash[0:], oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())
	})
})

func whoamiWithToken(token string, oc *exutil.CLI) (*userv1.User, error) {
	bearerTokenConfig := rest.AnonymousClientConfig(oc.AdminConfig())
	bearerTokenConfig.BearerToken = token
	userClient, err := versioned.NewForConfig(bearerTokenConfig)
	o.Expect(err).NotTo(o.HaveOccurred())
	return userClient.UserV1().Users().Get(context.TODO(), "~", metav1.GetOptions{})
}
<commit_msg>e2e/oauth: deflake by using random tokens<commit_after>package oauth

import (
	"context"
	"crypto/sha256"
	"encoding/base64"
	"fmt"

	"github.com/pborman/uuid"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"
	"github.com/openshift/client-go/user/clientset/versioned"
	"k8s.io/apimachinery/pkg/api/errors"

	"k8s.io/client-go/rest"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	oauthv1 "github.com/openshift/api/oauth/v1"
	userv1 "github.com/openshift/api/user/v1"
	exutil "github.com/openshift/origin/test/extended/util"
)

var _ = g.Describe("[sig-auth][Feature:OAuthServer] OAuth Authenticator", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("oauth-access-token-e2e-test")
	ctx := context.Background()

	g.It(fmt.Sprintf("accepts classic non-prefixed access tokens"), func() {
		user, err := oc.AdminUserClient().UserV1().Users().Create(ctx, &userv1.User{
			TypeMeta: metav1.TypeMeta{},
			ObjectMeta: metav1.ObjectMeta{
				Name: "oauth-access-token-e2e-test-user-classical",
			},
			FullName: "test user",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(userv1.GroupVersion.WithResource("users"), user)

		g.By("creating a classic oauth access token")
		token := base64.RawURLEncoding.EncodeToString([]byte(uuid.New()))
		classicTokenObject, err := oc.AdminOauthClient().OauthV1().OAuthAccessTokens().Create(ctx, &oauthv1.OAuthAccessToken{
			ObjectMeta: metav1.ObjectMeta{
				Name: token,
			},
			UserName:    user.Name,
			UserUID:     string(user.UID),
			ClientName:  "openshift-challenging-client",
			Scopes:      []string{"user:info"},
			RedirectURI: "https://127.0.0.1:12000/oauth/token/implicit",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(oauthv1.GroupVersion.WithResource("oauthaccesstokens"), classicTokenObject)

		g.By("authenticating using the classic access token as bearer token")
		gotUser, err := whoamiWithToken(token, oc)
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(gotUser.Name).To(o.Equal(user.Name))

		g.By("not-authenticating using a sha256 prefixed access token as bearer token")
		_, err = whoamiWithToken("sha256:"+token, oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())
	})

	g.It(fmt.Sprintf("accepts sha256 access tokens"), func() {
		user, err := oc.AdminUserClient().UserV1().Users().Create(ctx, &userv1.User{
			TypeMeta: metav1.TypeMeta{},
			ObjectMeta: metav1.ObjectMeta{
				Name: "oauth-access-token-e2e-test-user-sha256",
			},
			FullName: "test user",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(userv1.GroupVersion.WithResource("users"), user)

		g.By("creating a classic oauth access token")
		token := base64.RawURLEncoding.EncodeToString([]byte(uuid.New()))
		bs := sha256.Sum256([]byte(token))
		hash := base64.RawURLEncoding.EncodeToString(bs[:])
		classicTokenObject, err := oc.AdminOauthClient().OauthV1().OAuthAccessTokens().Create(ctx, &oauthv1.OAuthAccessToken{
			ObjectMeta: metav1.ObjectMeta{
				Name: "sha256:" + hash[0:],
			},
			UserName:    user.Name,
			UserUID:     string(user.UID),
			ClientName:  "openshift-challenging-client",
			Scopes:      []string{"user:info"},
			RedirectURI: "https://127.0.0.1:12000/oauth/token/implicit",
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		oc.AddResourceToDelete(oauthv1.GroupVersion.WithResource("oauthaccesstokens"), classicTokenObject)

		g.By("authenticating using the sha256 prefixed access token as bearer token")
		gotUser, err := whoamiWithToken("sha256:"+token, oc)
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(gotUser.Name).To(o.Equal(user.Name))

		g.By("not-authenticating using a sha256 prefixed hash as bearer token")
		_, err = whoamiWithToken("sha256:"+hash[0:], oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())

		g.By("not-authenticating using a non-prefixed token as bearer token")
		_, err = whoamiWithToken(token, oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())

		g.By("not-authenticating using a non-prefixed hash as bearer token")
		_, err = whoamiWithToken(hash[0:], oc)
		o.Expect(errors.IsUnauthorized(err)).To(o.BeTrue())
	})
})

func whoamiWithToken(token string, oc *exutil.CLI) (*userv1.User, error) {
	bearerTokenConfig := rest.AnonymousClientConfig(oc.AdminConfig())
	bearerTokenConfig.BearerToken = token
	userClient, err := versioned.NewForConfig(bearerTokenConfig)
	o.Expect(err).NotTo(o.HaveOccurred())
	return userClient.UserV1().Users().Get(context.TODO(), "~", metav1.GetOptions{})
}
<|endoftext|>


<commit_before>// compile -N -d=softfloat

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Issue 26163: dead store generated in late opt messes
// up store chain calculation.

package p

var i int
var A = ([]*int{})[i]

var F func(float64, complex128) int
var C chan complex128
var B = F(1, 1+(<-C))
<commit_msg>test: do not run (another) softfloat test with regabiargs<commit_after>// compile -N -d=softfloat -goexperiment noregabiargs

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Issue 26163: dead store generated in late opt messes
// up store chain calculation.

package p

var i int
var A = ([]*int{})[i]

var F func(float64, complex128) int
var C chan complex128
var B = F(1, 1+(<-C))
<|endoftext|>


<commit_before>package reddit

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strings"
)

type Submission struct {
	Title       string
	Url         string
	NumComments int `json:"num_comments"`
	Author      string
	IsSelf      bool `json:"is_self"`
	IsNSFW      bool `json:"over_18"`
	SelfText    string
	Created     float64 `json:"created_utc"`
	Score       int
	Ups         int
	Downs       int
	Sub         string `json:"subreddit"`
	*Thing
}

func (r *Submission) String() string {
	str := fmt.Sprintf(
		"Title: %s\n\t%d Up \n\t%d Down\n\tAuthor: %s\n\tSub: %s\n",
		r.Title,
		r.Ups,
		r.Downs,
		r.Author,
		r.Sub)
	return str
}

func (r *Submission) GetComments() []Comment {
	b, err := makeGetRequest(fmt.Sprintf(Urls["comment"], r.Sub, r.Id))
	if err != nil {
		panic(err)
	}
	cresp := make([]*commentsResponse, 2)
	err = json.Unmarshal(b, &cresp)
	comments := make([]Comment, len(cresp[1].Data.Children))
	for i, comment := range cresp[1].Data.Children {
		comments[i] = commentFromJson(comment.Data)
	}
	return comments
}

func (r *Submission) PostComment(user *Redditor, body string) error {
	if !user.IsLoggedIn() {
		return NotLoggedInError
	}
	data := &url.Values{
		"api_type": {"json"},
		"text":     {body},
		"uh":       {user.ModHash},
		"thing_id": {"t6_" + r.Id},
	}
	b, err := makePostRequest(ApiUrls["comment"], data)
	if err != nil {
		return err
	}
	errstruct := new(struct {
		Json struct {
			Errors [][]string
		}
	})
	err = json.Unmarshal(b, &errstruct)
	if err != nil {
		return err
	}
	if len(errstruct.Json.Errors) != 0 {
		return errors.New(strings.Join(errstruct.Json.Errors[0], ", "))
	}
	return nil
}
<commit_msg>added SubmissionUrl method to produce a url of the submission<commit_after>package reddit

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/url"
	"strings"
)

type Submission struct {
	Title       string
	Url         string
	NumComments int `json:"num_comments"`
	Author      string
	IsSelf      bool `json:"is_self"`
	IsNSFW      bool `json:"over_18"`
	SelfText    string
	Created     float64 `json:"created_utc"`
	Score       int
	Ups         int
	Downs       int
	Sub         string `json:"subreddit"`
	*Thing
}

func (r *Submission) String() string {
	str := fmt.Sprintf(
		"Title: %s\n\t%d Up \n\t%d Down\n\tAuthor: %s\n\tSub: %s\n",
		r.Title,
		r.Ups,
		r.Downs,
		r.Author,
		r.Sub)
	return str
}

func (r *Submission) SubmissionUrl() string {
	return fmt.Sprintf(Urls["comment"], r.Sub, r.Id)
}

func (r *Submission) GetComments() []Comment {
	b, err := makeGetRequest(fmt.Sprintf(Urls["comment"], r.Sub, r.Id))
	if err != nil {
		panic(err)
	}
	cresp := make([]*commentsResponse, 2)
	err = json.Unmarshal(b, &cresp)
	comments := make([]Comment, len(cresp[1].Data.Children))
	for i, comment := range cresp[1].Data.Children {
		comments[i] = commentFromJson(comment.Data)
	}
	return comments
}

func (r *Submission) PostComment(user *Redditor, body string) error {
	if !user.IsLoggedIn() {
		return NotLoggedInError
	}
	data := &url.Values{
		"api_type": {"json"},
		"text":     {body},
		"uh":       {user.ModHash},
		"thing_id": {"t6_" + r.Id},
	}
	b, err := makePostRequest(ApiUrls["comment"], data)
	if err != nil {
		return err
	}
	errstruct := new(struct {
		Json struct {
			Errors [][]string
		}
	})
	err = json.Unmarshal(b, &errstruct)
	if err != nil {
		return err
	}
	if len(errstruct.Json.Errors) != 0 {
		return errors.New(strings.Join(errstruct.Json.Errors[0], ", "))
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"image/color"
	"io"
	"sort"
	"strconv"
	"time"

	"github.com/MJKWoolnough/byteio"
	"github.com/MJKWoolnough/gopherjs/files"
	"github.com/MJKWoolnough/gopherjs/mutation"
	"github.com/MJKWoolnough/gopherjs/overlay"
	"github.com/MJKWoolnough/gopherjs/progress"
	"github.com/MJKWoolnough/gopherjs/xdom"
	"github.com/MJKWoolnough/gopherjs/xform"
	"github.com/MJKWoolnough/gopherjs/xjs"
	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/websocket"
	"honnef.co/go/js/dom"
)

func ReadError(r *byteio.StickyReader) error {
	s := ReadString(r)
	if r.Err != nil {
		return r.Err
	}
	return errors.New(s)
}

func WriteString(w *byteio.StickyWriter, s string) {
	w.WriteUint16(uint16(len(s)))
	w.Write([]byte(s))
}

func ReadString(r *byteio.StickyReader) string {
	length := r.ReadUint16()
	str := make([]byte, int(length))
	io.ReadFull(r, str)
	return string(str)
}

func transferFile(typeName, method string, typeID uint8, o *overlay.Overlay) dom.Node {
	name := xform.InputText("name", "")
	url := xform.InputRadio("url", "switch", true)
	upload := xform.InputRadio("upload", "switch", false)
	fileI := xform.InputUpload("")
	urlI := xform.InputURL("", "")
	s := xform.InputSubmit(method)

	name.Required = true

	typeFunc := func(dom.Event) {
		if url.Checked {
			urlI.Style().RemoveProperty("display")
			fileI.Style().SetProperty("display", "none", "")
			urlI.Required = true
			fileI.Required = false
			fileI.SetID("")
			urlI.SetID("file")
		} else {
			fileI.Style().RemoveProperty("display")
			urlI.Style().SetProperty("display", "none", "")
			fileI.Required = true
			urlI.Required = false
			urlI.SetID("")
			fileI.SetID("file")
		}
	}

	typeFunc(nil)

	url.AddEventListener("change", false, typeFunc)
	upload.AddEventListener("change", false, typeFunc)

	f := xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
		xjs.SetInnerText(xdom.Legend(), method+" "+typeName),
		xform.Label(typeName+" Name", "name"),
		name,
		xdom.Br(),

		xform.Label("URL", "url"),
		url,
		xdom.Br(),

		xform.Label("Upload", "upload"),
		upload,
		xdom.Br(),

		xform.Label("File", "file"),

		fileI,
		urlI,

		xdom.Br(),
		s,
	))

	s.AddEventListener("click", false, func(e dom.Event) {
		if name.Value == "" {
			return
		}
		if url.Checked {
			if urlI.Value == "" {
				return
			}
		} else if len(fileI.Files()) != 1 {
			return

		}
		s.Disabled = true
		name.Disabled = true
		url.Disabled = true
		upload.Disabled = true
		fileI.Disabled = true
		urlI.Disabled = true
		e.PreventDefault()
		go func() {
			d := xdom.Div()
			uo := overlay.New(d)
			uo.OnClose(func() {
				o.Close()
			})
			xjs.Body().AppendChild(uo)
			status := xdom.Div()
			d.AppendChild(xjs.SetInnerText(status, "Transferring..."))
			conn, err := websocket.Dial("ws://" + js.Global.Get("location").Get("host").String() + "/transfer")
			if err != nil {
				xjs.SetInnerText(status, err.Error())
				return
			}
			defer conn.Close()
			w := byteio.StickyWriter{Writer: byteio.LittleEndianWriter{Writer: conn}}
			r := byteio.StickyReader{Reader: byteio.LittleEndianReader{Reader: conn}}

			pb := progress.New(color.RGBA{255, 0, 0, 0}, color.RGBA{0, 0, 255, 0}, 400, 50)
			d.AppendChild(pb)

			if url.Checked {
				w.WriteUint8(typeID << 1)
				WriteString(&w, urlI.Value)
				length := int(r.ReadInt32())
				total := 0
				for total < length {
					switch v := r.ReadUint8(); v {
					case 1:
						i := int(r.ReadInt32())
						total += i
						pb.Percent(100 * total / length)
					default:
						xjs.SetInnerText(status, ReadError(&r).Error())
						return
					}
				}
			} else {
				f := files.NewFileReader(files.NewFile(fileI.Files()[0]))
				l := f.Len()
				if l == 0 {
					xjs.SetInnerText(status, "Zero-length file")
					return
				}
				w.WriteUint8(typeID<<1 | 1)
				w.WriteInt32(int32(l))
				io.Copy(&w, pb.Reader(f, l))
			}

			d.RemoveChild(pb)
			xjs.SetInnerText(status, "Checking File")

			WriteString(&w, name.Value)

			canvas := xdom.Canvas()
			ctx := canvas.GetContext2d()

			for {
				switch v := r.ReadUint8(); v {
				case 0:
					if r.Err != nil {
						xjs.SetInnerText(status, r.Err.Error())
					} else {
						xjs.SetInnerText(status, ReadError(&r).Error())
					}
					return
				case 1:
					jars := make([]xform.Option, r.ReadInt16())
					for i := range jars {
						jars[i] = xform.Option{
							Value: strconv.Itoa(i),
							Label: ReadString(&r),
						}
					}
					j := xform.SelectBox("jars", jars...)
					sel := xjs.SetInnerText(xdom.Button(), "Select")
					jo := overlay.New(xjs.AppendChildren(xdom.Div(), xjs.AppendChildren(xdom.Fieldset(),
						xjs.SetInnerText(xdom.Legend(), "Please select the server jar"),
						xform.Label("Jar File", "jars"),
						j,
						xdom.Br(),
						sel,
					)))
					c := make(chan int16, 0)
					done := false
					jo.OnClose(func() {
						if !done {
							done = true
							c <- -1
						}
					})
					sel.AddEventListener("click", false, func(dom.Event) {
						if !done {
							done = true
							v, err := strconv.Atoi(j.Value)
							if err != nil {
								v = -1
							}
							c <- int16(v)
							jo.Close()
						}
					})
					xjs.Body().AppendChild(jo)
					w.WriteInt16(<-c)
					close(c)
				case 2:
					w := r.ReadInt32()
					h := r.ReadInt32()
					canvas.Width = int(w)
					canvas.Height = int(h)
					d.AppendChild(canvas)
				case 3:
					xjs.SetInnerText(status, ReadString(&r))
				case 4:
					x := r.ReadInt32()
					y := r.ReadInt32()
					red := r.ReadUint8()
					green := r.ReadUint8()
					blue := r.ReadUint8()
					alpha := r.ReadUint8()
					ctx.FillStyle = "rgba(" + strconv.Itoa(int(red)) + ", " + strconv.Itoa(int(green)) + ", " + strconv.Itoa(int(blue)) + ", " + strconv.FormatFloat(float64(alpha)/255, 'f', -1, 32) + ")"
					ctx.FillRect(int(x), int(y), 1, 1)
				case 255:
					uo.Close()
					return
				}
			}

		}()
	})
	return f
}

func editProperties(c dom.Element, name string, id int, rpcGet func(int) (map[string]string, error), rpcSet func(id int, properties map[string]string) error) {
	sp, err := rpcGet(id)
	if err != nil {
		c.AppendChild(xjs.SetInnerText(xdom.Div(), "Failed to get properties: "+err.Error()))
		return
	}
	props := make(PropertyList, 0, len(sp))
	for k, v := range sp {
		props = append(props, [2]string{k, v})
	}
	sort.Sort(props)
	propE := make([][2]*dom.HTMLSpanElement, len(props))
	df := xjs.DocumentFragment()

	toggleFunc := func(k, v *dom.HTMLSpanElement, toggle *dom.HTMLInputElement) func(dom.Event) {
		return func(dom.Event) {
			if toggle.Value == "-" {
				k.SetContentEditable("false")
				v.SetContentEditable("false")
				k.Style().SetProperty("background-color", "#888", "")
				v.Style().SetProperty("background-color", "#888", "")
				toggle.Value = "+"
			} else {
				k.SetContentEditable("true")
				v.SetContentEditable("true")
				k.Style().RemoveProperty("background-color")
				v.Style().RemoveProperty("background-color")
				toggle.Value = "-"
			}
		}
	}

	for i, prop := range props {
		k := xform.InputSizeable("", prop[0])
		v := xform.InputSizeable("", prop[1])
		toggle := xform.InputButton("", "-")
		toggle.AddEventListener("click", false, toggleFunc(k, v, toggle))
		propE[i][0] = k
		propE[i][1] = v
		xjs.AppendChildren(df,
			toggle,
			k,
			xjs.SetInnerText(xdom.Span(), "="),
			v,
			xdom.Br(),
		)
	}

	add := xform.InputButton("", "Add")
	submit := xform.InputButton("", "Save")
	fs := xjs.AppendChildren(xdom.Fieldset(),
		xjs.SetInnerText(xdom.Legend(), name+" Properties"),
		df,
		add,
		submit,
	)

	add.AddEventListener("click", false, func(dom.Event) {
		k := xform.InputSizeable("", "")
		v := xform.InputSizeable("", "")
		toggle := xform.InputButton("", "-")
		toggle.AddEventListener("click", false, toggleFunc(k, v, toggle))
		propE = append(propE, [2]*dom.HTMLSpanElement{k, v})
		fs.InsertBefore(toggle, add)
		fs.InsertBefore(k, add)
		fs.InsertBefore(xjs.SetInnerText(xdom.Span(), "="), add)
		fs.InsertBefore(v, add)
		fs.InsertBefore(xdom.Br(), add)
	})

	submit.AddEventListener("click", false, func(dom.Event) {
		submit.Disabled = true
		props := make(map[string]string, len(propE))
		for _, spans := range propE {
			if spans[0].IsContentEditable() {
				props[spans[0].TextContent()] = spans[1].TextContent()
			}
		}
		go func() {
			err := rpcSet(id, props)
			if err != nil {
				xjs.Alert("Error setting "+name+" properties: %s", err)
				return
			}
			span := xdom.Span()
			span.Style().Set("color", "#f00")
			fs.AppendChild(xjs.SetInnerText(span, "Saved!"))
			time.Sleep(5 * time.Second)
			fs.RemoveChild(span)
			submit.Disabled = false
		}()
	})

	xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(), fs))
}

func misc(mType string, id int, o *overlay.Overlay, deleteFunc func(int) error) func(dom.Element) {
	return func(c dom.Element) {
		download := xdom.A()
		download.Href = "http://" + js.Global.Get("location").Get("host").String() + "/download/" + mType + "/" + strconv.Itoa(id) + ".zip"
		del := xdom.Button()
		del.AddEventListener("click", false, func(dom.Event) {
			if dom.GetWindow().Confirm("Are you sure?") {
				err := deleteFunc(id)
				if err != nil {
					xjs.Alert("Error while deleting %s: %s", mType, err)
				} else {
					o.Close()
				}
			}
		})
		xjs.AppendChildren(c,
			xjs.AppendChildren(xdom.Fieldset(),
				xjs.SetInnerText(xdom.Legend(), "Download"),
				xjs.SetInnerText(xdom.Div(), "Click the following link to download the "+mType+" as a zip file."),
				xjs.SetInnerText(download, download.Href),
			),
			xjs.AppendChildren(xdom.Fieldset(),
				xjs.SetInnerText(xdom.Legend(), "Delete"),
				xjs.SetInnerText(xdom.Div(), "The following button will permanently delete the "+mType+" (this cannot be undone)."),
				xjs.SetInnerText(del, "Delete "+mType),
			),
		)
	}
}

func registerUpdateStopper(c dom.Element, updateStop chan struct{}) {
	if c.ParentNode() != nil {
		mutation.New(func(rs []*mutation.Record, o *mutation.Observer) {
			if len(rs) > 0 {
				for _, r := range rs {
					for _, n := range r.RemovedNodes() {
						if c.IsEqualNode(n) {
							o.Disconnect()
							close(updateStop)
							return
						}
					}
				}
			}
		}).Observe(c.ParentNode(), mutation.ObserverInit{ChildList: true})
	}
}

func updateSleep(forceUpdate, updateStop <-chan struct{}) bool {
	t := time.NewTicker(time.Second * 30)
	defer t.Stop()
	select {
	case <-updateStop:
		return false
	case <-forceUpdate:
	case <-t.C:
	}
	return true
}
<commit_msg>Added target to download link in order to stop page unloading<commit_after>package main

import (
	"errors"
	"image/color"
	"io"
	"sort"
	"strconv"
	"time"

	"github.com/MJKWoolnough/byteio"
	"github.com/MJKWoolnough/gopherjs/files"
	"github.com/MJKWoolnough/gopherjs/mutation"
	"github.com/MJKWoolnough/gopherjs/overlay"
	"github.com/MJKWoolnough/gopherjs/progress"
	"github.com/MJKWoolnough/gopherjs/xdom"
	"github.com/MJKWoolnough/gopherjs/xform"
	"github.com/MJKWoolnough/gopherjs/xjs"
	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/websocket"
	"honnef.co/go/js/dom"
)

func ReadError(r *byteio.StickyReader) error {
	s := ReadString(r)
	if r.Err != nil {
		return r.Err
	}
	return errors.New(s)
}

func WriteString(w *byteio.StickyWriter, s string) {
	w.WriteUint16(uint16(len(s)))
	w.Write([]byte(s))
}

func ReadString(r *byteio.StickyReader) string {
	length := r.ReadUint16()
	str := make([]byte, int(length))
	io.ReadFull(r, str)
	return string(str)
}

func transferFile(typeName, method string, typeID uint8, o *overlay.Overlay) dom.Node {
	name := xform.InputText("name", "")
	url := xform.InputRadio("url", "switch", true)
	upload := xform.InputRadio("upload", "switch", false)
	fileI := xform.InputUpload("")
	urlI := xform.InputURL("", "")
	s := xform.InputSubmit(method)

	name.Required = true

	typeFunc := func(dom.Event) {
		if url.Checked {
			urlI.Style().RemoveProperty("display")
			fileI.Style().SetProperty("display", "none", "")
			urlI.Required = true
			fileI.Required = false
			fileI.SetID("")
			urlI.SetID("file")
		} else {
			fileI.Style().RemoveProperty("display")
			urlI.Style().SetProperty("display", "none", "")
			fileI.Required = true
			urlI.Required = false
			urlI.SetID("")
			fileI.SetID("file")
		}
	}

	typeFunc(nil)

	url.AddEventListener("change", false, typeFunc)
	upload.AddEventListener("change", false, typeFunc)

	f := xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
		xjs.SetInnerText(xdom.Legend(), method+" "+typeName),
		xform.Label(typeName+" Name", "name"),
		name,
		xdom.Br(),

		xform.Label("URL", "url"),
		url,
		xdom.Br(),

		xform.Label("Upload", "upload"),
		upload,
		xdom.Br(),

		xform.Label("File", "file"),

		fileI,
		urlI,

		xdom.Br(),
		s,
	))

	s.AddEventListener("click", false, func(e dom.Event) {
		if name.Value == "" {
			return
		}
		if url.Checked {
			if urlI.Value == "" {
				return
			}
		} else if len(fileI.Files()) != 1 {
			return

		}
		s.Disabled = true
		name.Disabled = true
		url.Disabled = true
		upload.Disabled = true
		fileI.Disabled = true
		urlI.Disabled = true
		e.PreventDefault()
		go func() {
			d := xdom.Div()
			uo := overlay.New(d)
			uo.OnClose(func() {
				o.Close()
			})
			xjs.Body().AppendChild(uo)
			status := xdom.Div()
			d.AppendChild(xjs.SetInnerText(status, "Transferring..."))
			conn, err := websocket.Dial("ws://" + js.Global.Get("location").Get("host").String() + "/transfer")
			if err != nil {
				xjs.SetInnerText(status, err.Error())
				return
			}
			defer conn.Close()
			w := byteio.StickyWriter{Writer: byteio.LittleEndianWriter{Writer: conn}}
			r := byteio.StickyReader{Reader: byteio.LittleEndianReader{Reader: conn}}

			pb := progress.New(color.RGBA{255, 0, 0, 0}, color.RGBA{0, 0, 255, 0}, 400, 50)
			d.AppendChild(pb)

			if url.Checked {
				w.WriteUint8(typeID << 1)
				WriteString(&w, urlI.Value)
				length := int(r.ReadInt32())
				total := 0
				for total < length {
					switch v := r.ReadUint8(); v {
					case 1:
						i := int(r.ReadInt32())
						total += i
						pb.Percent(100 * total / length)
					default:
						xjs.SetInnerText(status, ReadError(&r).Error())
						return
					}
				}
			} else {
				f := files.NewFileReader(files.NewFile(fileI.Files()[0]))
				l := f.Len()
				if l == 0 {
					xjs.SetInnerText(status, "Zero-length file")
					return
				}
				w.WriteUint8(typeID<<1 | 1)
				w.WriteInt32(int32(l))
				io.Copy(&w, pb.Reader(f, l))
			}

			d.RemoveChild(pb)
			xjs.SetInnerText(status, "Checking File")

			WriteString(&w, name.Value)

			canvas := xdom.Canvas()
			ctx := canvas.GetContext2d()

			for {
				switch v := r.ReadUint8(); v {
				case 0:
					if r.Err != nil {
						xjs.SetInnerText(status, r.Err.Error())
					} else {
						xjs.SetInnerText(status, ReadError(&r).Error())
					}
					return
				case 1:
					jars := make([]xform.Option, r.ReadInt16())
					for i := range jars {
						jars[i] = xform.Option{
							Value: strconv.Itoa(i),
							Label: ReadString(&r),
						}
					}
					j := xform.SelectBox("jars", jars...)
					sel := xjs.SetInnerText(xdom.Button(), "Select")
					jo := overlay.New(xjs.AppendChildren(xdom.Div(), xjs.AppendChildren(xdom.Fieldset(),
						xjs.SetInnerText(xdom.Legend(), "Please select the server jar"),
						xform.Label("Jar File", "jars"),
						j,
						xdom.Br(),
						sel,
					)))
					c := make(chan int16, 0)
					done := false
					jo.OnClose(func() {
						if !done {
							done = true
							c <- -1
						}
					})
					sel.AddEventListener("click", false, func(dom.Event) {
						if !done {
							done = true
							v, err := strconv.Atoi(j.Value)
							if err != nil {
								v = -1
							}
							c <- int16(v)
							jo.Close()
						}
					})
					xjs.Body().AppendChild(jo)
					w.WriteInt16(<-c)
					close(c)
				case 2:
					w := r.ReadInt32()
					h := r.ReadInt32()
					canvas.Width = int(w)
					canvas.Height = int(h)
					d.AppendChild(canvas)
				case 3:
					xjs.SetInnerText(status, ReadString(&r))
				case 4:
					x := r.ReadInt32()
					y := r.ReadInt32()
					red := r.ReadUint8()
					green := r.ReadUint8()
					blue := r.ReadUint8()
					alpha := r.ReadUint8()
					ctx.FillStyle = "rgba(" + strconv.Itoa(int(red)) + ", " + strconv.Itoa(int(green)) + ", " + strconv.Itoa(int(blue)) + ", " + strconv.FormatFloat(float64(alpha)/255, 'f', -1, 32) + ")"
					ctx.FillRect(int(x), int(y), 1, 1)
				case 255:
					uo.Close()
					return
				}
			}

		}()
	})
	return f
}

func editProperties(c dom.Element, name string, id int, rpcGet func(int) (map[string]string, error), rpcSet func(id int, properties map[string]string) error) {
	sp, err := rpcGet(id)
	if err != nil {
		c.AppendChild(xjs.SetInnerText(xdom.Div(), "Failed to get properties: "+err.Error()))
		return
	}
	props := make(PropertyList, 0, len(sp))
	for k, v := range sp {
		props = append(props, [2]string{k, v})
	}
	sort.Sort(props)
	propE := make([][2]*dom.HTMLSpanElement, len(props))
	df := xjs.DocumentFragment()

	toggleFunc := func(k, v *dom.HTMLSpanElement, toggle *dom.HTMLInputElement) func(dom.Event) {
		return func(dom.Event) {
			if toggle.Value == "-" {
				k.SetContentEditable("false")
				v.SetContentEditable("false")
				k.Style().SetProperty("background-color", "#888", "")
				v.Style().SetProperty("background-color", "#888", "")
				toggle.Value = "+"
			} else {
				k.SetContentEditable("true")
				v.SetContentEditable("true")
				k.Style().RemoveProperty("background-color")
				v.Style().RemoveProperty("background-color")
				toggle.Value = "-"
			}
		}
	}

	for i, prop := range props {
		k := xform.InputSizeable("", prop[0])
		v := xform.InputSizeable("", prop[1])
		toggle := xform.InputButton("", "-")
		toggle.AddEventListener("click", false, toggleFunc(k, v, toggle))
		propE[i][0] = k
		propE[i][1] = v
		xjs.AppendChildren(df,
			toggle,
			k,
			xjs.SetInnerText(xdom.Span(), "="),
			v,
			xdom.Br(),
		)
	}

	add := xform.InputButton("", "Add")
	submit := xform.InputButton("", "Save")
	fs := xjs.AppendChildren(xdom.Fieldset(),
		xjs.SetInnerText(xdom.Legend(), name+" Properties"),
		df,
		add,
		submit,
	)

	add.AddEventListener("click", false, func(dom.Event) {
		k := xform.InputSizeable("", "")
		v := xform.InputSizeable("", "")
		toggle := xform.InputButton("", "-")
		toggle.AddEventListener("click", false, toggleFunc(k, v, toggle))
		propE = append(propE, [2]*dom.HTMLSpanElement{k, v})
		fs.InsertBefore(toggle, add)
		fs.InsertBefore(k, add)
		fs.InsertBefore(xjs.SetInnerText(xdom.Span(), "="), add)
		fs.InsertBefore(v, add)
		fs.InsertBefore(xdom.Br(), add)
	})

	submit.AddEventListener("click", false, func(dom.Event) {
		submit.Disabled = true
		props := make(map[string]string, len(propE))
		for _, spans := range propE {
			if spans[0].IsContentEditable() {
				props[spans[0].TextContent()] = spans[1].TextContent()
			}
		}
		go func() {
			err := rpcSet(id, props)
			if err != nil {
				xjs.Alert("Error setting "+name+" properties: %s", err)
				return
			}
			span := xdom.Span()
			span.Style().Set("color", "#f00")
			fs.AppendChild(xjs.SetInnerText(span, "Saved!"))
			time.Sleep(5 * time.Second)
			fs.RemoveChild(span)
			submit.Disabled = false
		}()
	})

	xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(), fs))
}

func misc(mType string, id int, o *overlay.Overlay, deleteFunc func(int) error) func(dom.Element) {
	return func(c dom.Element) {
		download := xdom.A()
		download.Href = "http://" + js.Global.Get("location").Get("host").String() + "/download/" + mType + "/" + strconv.Itoa(id) + ".zip"
		download.Target = "_blank"
		del := xdom.Button()
		del.AddEventListener("click", false, func(dom.Event) {
			if dom.GetWindow().Confirm("Are you sure?") {
				err := deleteFunc(id)
				if err != nil {
					xjs.Alert("Error while deleting %s: %s", mType, err)
				} else {
					o.Close()
				}
			}
		})
		xjs.AppendChildren(c,
			xjs.AppendChildren(xdom.Fieldset(),
				xjs.SetInnerText(xdom.Legend(), "Download"),
				xjs.SetInnerText(xdom.Div(), "Click the following link to download the "+mType+" as a zip file."),
				xjs.SetInnerText(download, download.Href),
			),
			xjs.AppendChildren(xdom.Fieldset(),
				xjs.SetInnerText(xdom.Legend(), "Delete"),
				xjs.SetInnerText(xdom.Div(), "The following button will permanently delete the "+mType+" (this cannot be undone)."),
				xjs.SetInnerText(del, "Delete "+mType),
			),
		)
	}
}

func registerUpdateStopper(c dom.Element, updateStop chan struct{}) {
	if c.ParentNode() != nil {
		mutation.New(func(rs []*mutation.Record, o *mutation.Observer) {
			if len(rs) > 0 {
				for _, r := range rs {
					for _, n := range r.RemovedNodes() {
						if c.IsEqualNode(n) {
							o.Disconnect()
							close(updateStop)
							return
						}
					}
				}
			}
		}).Observe(c.ParentNode(), mutation.ObserverInit{ChildList: true})
	}
}

func updateSleep(forceUpdate, updateStop <-chan struct{}) bool {
	t := time.NewTicker(time.Second * 30)
	defer t.Stop()
	select {
	case <-updateStop:
		return false
	case <-forceUpdate:
	case <-t.C:
	}
	return true
}
<|endoftext|>


<commit_before>package migemo

import (
	"bytes"
	"container/list"
	"regexp"
	"unicode/utf8"

	"github.com/koron/gelatin/trie"
)

func (m *matcher) Pattern() (pattern string, err error) {
	if m.patterned {
		return pattern, nil
	}
	b := new(bytes.Buffer)
	err = m.writePattern(b, m.trie.Root())
	if err != nil {
		return "", err
	}
	m.pattern = b.String()
	m.patterned = true
	return m.pattern, nil
}

func (m *matcher) writePattern(b *bytes.Buffer, n trie.Node) error {
	labels, child_nodes := m.splitLabels(n)
	// Output group in.
	grouped := false
	c0 := utf8.RuneCountInString(labels)
	c1 := child_nodes.Len()
	if c0+c1 > 1 && c1 > 0 {
		grouped = true
		b.WriteString(m.options.OpGroupIn)
	}
	// Output nodes which doesn't have any children.
	if c0 > 0 {
		if c0 > 1 {
			b.WriteString(m.options.OpClassIn)
			b.WriteString(m.quoteMeta(labels))
			b.WriteString(m.options.OpClassOut)
		} else {
			b.WriteString(m.quoteMeta(labels))
		}
	}
	// Output nodes which have some children.
	if c1 > 0 {
		first := c0 == 0
		for e := child_nodes.Front(); e != nil; e = e.Next() {
			if !first {
				b.WriteString(m.options.OpOr)
			} else {
				first = false
			}
			child := e.Value.(*trie.TernaryNode)
			b.WriteString(m.quoteMeta(string(child.Label())))
			b.WriteString(m.options.OpWSpaces)
			m.writePattern(b, child)
		}
	}
	// Output group out.
	if grouped {
		b.WriteString(m.options.OpGroupOut)
	}
	return nil
}

// splitLabels split children which have children or not.
func (m *matcher) splitLabels(n trie.Node) (label string, nodes *list.List) {
	l := list.New()
	b := new(bytes.Buffer)
	n.Each(func(t trie.Node) bool {
		if t.HasChildren() {
			l.PushBack(t)
		} else {
			b.WriteRune(t.Label())
		}
		return true
	})
	return b.String(), l
}

func (m *matcher) quoteMeta(s string) string {
	// Quote regexp meta chars.
	return regexp.QuoteMeta(s)
}
<commit_msg>Pattern() second call didn't work<commit_after>package migemo

import (
	"bytes"
	"container/list"
	"regexp"
	"unicode/utf8"

	"github.com/koron/gelatin/trie"
)

func (m *matcher) Pattern() (pattern string, err error) {
	if m.patterned {
		return m.pattern, nil
	}
	b := new(bytes.Buffer)
	err = m.writePattern(b, m.trie.Root())
	if err != nil {
		return "", err
	}
	m.pattern = b.String()
	m.patterned = true
	return m.pattern, nil
}

func (m *matcher) writePattern(b *bytes.Buffer, n trie.Node) error {
	labels, child_nodes := m.splitLabels(n)
	// Output group in.
	grouped := false
	c0 := utf8.RuneCountInString(labels)
	c1 := child_nodes.Len()
	if c0+c1 > 1 && c1 > 0 {
		grouped = true
		b.WriteString(m.options.OpGroupIn)
	}
	// Output nodes which doesn't have any children.
	if c0 > 0 {
		if c0 > 1 {
			b.WriteString(m.options.OpClassIn)
			b.WriteString(m.quoteMeta(labels))
			b.WriteString(m.options.OpClassOut)
		} else {
			b.WriteString(m.quoteMeta(labels))
		}
	}
	// Output nodes which have some children.
	if c1 > 0 {
		first := c0 == 0
		for e := child_nodes.Front(); e != nil; e = e.Next() {
			if !first {
				b.WriteString(m.options.OpOr)
			} else {
				first = false
			}
			child := e.Value.(*trie.TernaryNode)
			b.WriteString(m.quoteMeta(string(child.Label())))
			b.WriteString(m.options.OpWSpaces)
			m.writePattern(b, child)
		}
	}
	// Output group out.
	if grouped {
		b.WriteString(m.options.OpGroupOut)
	}
	return nil
}

// splitLabels split children which have children or not.
func (m *matcher) splitLabels(n trie.Node) (label string, nodes *list.List) {
	l := list.New()
	b := new(bytes.Buffer)
	n.Each(func(t trie.Node) bool {
		if t.HasChildren() {
			l.PushBack(t)
		} else {
			b.WriteRune(t.Label())
		}
		return true
	})
	return b.String(), l
}

func (m *matcher) quoteMeta(s string) string {
	// Quote regexp meta chars.
	return regexp.QuoteMeta(s)
}
<|endoftext|>


<commit_before>package svg

import (
	strconvStdlib "strconv"

	"github.com/tdewolff/minify"
	"github.com/tdewolff/parse"
	"github.com/tdewolff/strconv"
)

type PathData struct {
	o *Minifier

	x, y        float64
	coords      [][]byte
	coordFloats []float64

	state       PathDataState
	curBuffer   []byte
	altBuffer   []byte
	coordBuffer []byte
}

type PathDataState struct {
	cmd            byte
	prevDigit      bool
	prevDigitIsInt bool
}

func NewPathData(o *Minifier) *PathData {
	return &PathData{o: o}
}

func (p *PathData) ShortenPathData(b []byte) []byte {
	var x0, y0 float64
	var cmd byte

	p.x, p.y = 0.0, 0.0
	p.coords = p.coords[:0]
	p.coordFloats = p.coordFloats[:0]
	p.state = PathDataState{}

	j := 0
	for i := 0; i < len(b); i++ {
		c := b[i]
		if c == ' ' || c == ',' || c == '\n' || c == '\r' || c == '\t' {
			continue
		} else if c >= 'A' && (cmd == 0 || cmd != c || c == 'M' || c == 'm') { // any command
			if cmd != 0 {
				j += p.copyInstruction(b[j:], cmd)
				if cmd == 'M' || cmd == 'm' {
					x0 = p.x
					y0 = p.y
				} else if cmd == 'Z' || cmd == 'z' {
					p.x = x0
					p.y = y0
				}
			}
			cmd = c
			p.coords = p.coords[:0]
			p.coordFloats = p.coordFloats[:0]
		} else if n := parse.Number(b[i:]); n > 0 {
			f, _ := strconv.ParseFloat(b[i : i+n])
			p.coords = append(p.coords, b[i:i+n])
			p.coordFloats = append(p.coordFloats, f)
			i += n - 1
		}
	}
	if cmd != 0 {
		j += p.copyInstruction(b[j:], cmd)
	}
	return b[:j]
}

func (p *PathData) copyInstruction(b []byte, cmd byte) int {
	n := len(p.coords)
	if n == 0 {
		if cmd == 'Z' || cmd == 'z' {
			b[0] = 'z'
			return 1
		}
		return 0
	}
	isRelCmd := cmd >= 'a'

	// get new cursor coordinates
	di := 0
	if (cmd == 'M' || cmd == 'm' || cmd == 'L' || cmd == 'l' || cmd == 'T' || cmd == 't') && n%2 == 0 {
		di = 2
		// reprint M always, as the first pair is a move but subsequent pairs are L
		if cmd == 'M' || cmd == 'm' {
			p.state.cmd = byte(0)
		}
	} else if cmd == 'H' || cmd == 'h' || cmd == 'V' || cmd == 'v' {
		di = 1
	} else if (cmd == 'S' || cmd == 's' || cmd == 'Q' || cmd == 'q') && n%4 == 0 {
		di = 4
	} else if (cmd == 'C' || cmd == 'c') && n%6 == 0 {
		di = 6
	} else if (cmd == 'A' || cmd == 'a') && n%7 == 0 {
		di = 7
	} else {
		return 0
	}

	j := 0
	origCmd := cmd
	ax, ay := 0.0, 0.0
	for i := 0; i < n; i += di {
		// subsequent coordinate pairs for M are really L
		if i > 0 && (origCmd == 'M' || origCmd == 'm') {
			origCmd = 'L' + (origCmd - 'M')
		}

		cmd = origCmd
		coords := p.coords[i : i+di]
		coordFloats := p.coordFloats[i : i+di]

		if cmd == 'H' || cmd == 'h' {
			ax = coordFloats[di-1]
			if isRelCmd {
				ay = 0
			} else {
				ay = p.y
			}
		} else if cmd == 'V' || cmd == 'v' {
			if isRelCmd {
				ax = 0
			} else {
				ax = p.x
			}
			ay = coordFloats[di-1]
		} else {
			ax = coordFloats[di-2]
			ay = coordFloats[di-1]
		}

		// switch from L to H or V whenever possible
		if cmd == 'L' || cmd == 'l' {
			if isRelCmd {
				if coordFloats[0] == 0 {
					cmd = 'v'
					coords = coords[1:]
					coordFloats = coordFloats[1:]
				} else if coordFloats[1] == 0 {
					cmd = 'h'
					coords = coords[:1]
					coordFloats = coordFloats[:1]
				}
			} else {
				if coordFloats[0] == p.x {
					cmd = 'V'
					coords = coords[1:]
					coordFloats = coordFloats[1:]
				} else if coordFloats[1] == p.y {
					cmd = 'H'
					coords = coords[:1]
					coordFloats = coordFloats[:1]
				}
			}
		}

		// make a current and alternated path with absolute/relative altered
		var curState, altState PathDataState
		curState = p.shortenCurPosInstruction(cmd, coords)
		if isRelCmd {
			altState = p.shortenAltPosInstruction(cmd-'a'+'A', coordFloats, p.x, p.y)
		} else {
			altState = p.shortenAltPosInstruction(cmd-'A'+'a', coordFloats, -p.x, -p.y)
		}

		// choose shortest, relative or absolute path?
		if len(p.altBuffer) < len(p.curBuffer) {
			j += copy(b[j:], p.altBuffer)
			p.state = altState
		} else {
			j += copy(b[j:], p.curBuffer)
			p.state = curState
		}

		if isRelCmd {
			p.x += ax
			p.y += ay
		} else {
			p.x = ax
			p.y = ay
		}
	}
	return j
}

func (p *PathData) shortenCurPosInstruction(cmd byte, coords [][]byte) PathDataState {
	state := p.state
	p.curBuffer = p.curBuffer[:0]
	if cmd != state.cmd && !(state.cmd == 'M' && cmd == 'L' || state.cmd == 'm' && cmd == 'l') {
		p.curBuffer = append(p.curBuffer, cmd)
		state.cmd = cmd
		state.prevDigit = false
		state.prevDigitIsInt = false
	}
	for _, coord := range coords {
		coord := minify.Number(coord, -1)
		if state.prevDigit && (coord[0] >= '0' && coord[0] <= '9' || coord[0] == '.' && state.prevDigitIsInt) {
			p.curBuffer = append(p.curBuffer, ' ')
		}
		state.prevDigit = true
		state.prevDigitIsInt = true
		for _, c := range coord {
			if c == '.' || c == 'e' || c == 'E' {
				state.prevDigitIsInt = false
				break
			}
		}
		p.curBuffer = append(p.curBuffer, coord...)
	}
	return state
}

func (p *PathData) shortenAltPosInstruction(cmd byte, coordFloats []float64, x, y float64) PathDataState {
	state := p.state
	p.altBuffer = p.altBuffer[:0]
	if cmd != state.cmd && !(state.cmd == 'M' && cmd == 'L' || state.cmd == 'm' && cmd == 'l') {
		p.altBuffer = append(p.altBuffer, cmd)
		state.cmd = cmd
		state.prevDigit = false
		state.prevDigitIsInt = false
	}
	for i, f := range coordFloats {
		if cmd == 'L' || cmd == 'l' || cmd == 'C' || cmd == 'c' || cmd == 'S' || cmd == 's' || cmd == 'Q' || cmd == 'q' || cmd == 'T' || cmd == 't' || cmd == 'M' || cmd == 'm' {
			if i%2 == 0 {
				f += x
			} else {
				f += y
			}
		} else if cmd == 'H' || cmd == 'h' {
			f += x
		} else if cmd == 'V' || cmd == 'v' {
			f += y
		} else if cmd == 'A' || cmd == 'a' {
			if i%7 == 5 {
				f += x
			} else if i%7 == 6 {
				f += y
			}
		} else {
			continue
		}

		coord, ok := strconv.AppendFloat(p.coordBuffer[:0], f, 6)
		// if string(coord) != strconvStdlib.FormatFloat(f, 'g', 6, 64) {
		// 	fmt.Println(string(coord), "!=", strconvStdlib.FormatFloat(f, 'g', 6, 64))
		// }
		p.coordBuffer = coord // keep memory
		if !ok {
			p.coordBuffer = strconvStdlib.AppendFloat(p.coordBuffer[:0], f, 'g', 6, 64)
			coord = minify.Number(p.coordBuffer, -1)
		}

		if state.prevDigit && (coord[0] >= '0' && coord[0] <= '9' || coord[0] == '.' && state.prevDigitIsInt) {
			p.altBuffer = append(p.altBuffer, ' ')
		}
		state.prevDigit = true
		state.prevDigitIsInt = true
		for _, c := range coord {
			if c == '.' || c == 'e' || c == 'E' {
				state.prevDigitIsInt = false
				break
			}
		}
		p.altBuffer = append(p.altBuffer, coord...)
	}
	return state
}
<commit_msg>Pass Decimals to APpendFloat<commit_after>package svg

import (
	strconvStdlib "strconv"

	"github.com/tdewolff/minify"
	"github.com/tdewolff/parse"
	"github.com/tdewolff/strconv"
)

type PathData struct {
	o *Minifier

	x, y        float64
	coords      [][]byte
	coordFloats []float64

	state       PathDataState
	curBuffer   []byte
	altBuffer   []byte
	coordBuffer []byte
}

type PathDataState struct {
	cmd            byte
	prevDigit      bool
	prevDigitIsInt bool
}

func NewPathData(o *Minifier) *PathData {
	return &PathData{o: o}
}

func (p *PathData) ShortenPathData(b []byte) []byte {
	var x0, y0 float64
	var cmd byte

	p.x, p.y = 0.0, 0.0
	p.coords = p.coords[:0]
	p.coordFloats = p.coordFloats[:0]
	p.state = PathDataState{}

	j := 0
	for i := 0; i < len(b); i++ {
		c := b[i]
		if c == ' ' || c == ',' || c == '\n' || c == '\r' || c == '\t' {
			continue
		} else if c >= 'A' && (cmd == 0 || cmd != c || c == 'M' || c == 'm') { // any command
			if cmd != 0 {
				j += p.copyInstruction(b[j:], cmd)
				if cmd == 'M' || cmd == 'm' {
					x0 = p.x
					y0 = p.y
				} else if cmd == 'Z' || cmd == 'z' {
					p.x = x0
					p.y = y0
				}
			}
			cmd = c
			p.coords = p.coords[:0]
			p.coordFloats = p.coordFloats[:0]
		} else if n := parse.Number(b[i:]); n > 0 {
			f, _ := strconv.ParseFloat(b[i : i+n])
			p.coords = append(p.coords, b[i:i+n])
			p.coordFloats = append(p.coordFloats, f)
			i += n - 1
		}
	}
	if cmd != 0 {
		j += p.copyInstruction(b[j:], cmd)
	}
	return b[:j]
}

func (p *PathData) copyInstruction(b []byte, cmd byte) int {
	n := len(p.coords)
	if n == 0 {
		if cmd == 'Z' || cmd == 'z' {
			b[0] = 'z'
			return 1
		}
		return 0
	}
	isRelCmd := cmd >= 'a'

	// get new cursor coordinates
	di := 0
	if (cmd == 'M' || cmd == 'm' || cmd == 'L' || cmd == 'l' || cmd == 'T' || cmd == 't') && n%2 == 0 {
		di = 2
		// reprint M always, as the first pair is a move but subsequent pairs are L
		if cmd == 'M' || cmd == 'm' {
			p.state.cmd = byte(0)
		}
	} else if cmd == 'H' || cmd == 'h' || cmd == 'V' || cmd == 'v' {
		di = 1
	} else if (cmd == 'S' || cmd == 's' || cmd == 'Q' || cmd == 'q') && n%4 == 0 {
		di = 4
	} else if (cmd == 'C' || cmd == 'c') && n%6 == 0 {
		di = 6
	} else if (cmd == 'A' || cmd == 'a') && n%7 == 0 {
		di = 7
	} else {
		return 0
	}

	j := 0
	origCmd := cmd
	ax, ay := 0.0, 0.0
	for i := 0; i < n; i += di {
		// subsequent coordinate pairs for M are really L
		if i > 0 && (origCmd == 'M' || origCmd == 'm') {
			origCmd = 'L' + (origCmd - 'M')
		}

		cmd = origCmd
		coords := p.coords[i : i+di]
		coordFloats := p.coordFloats[i : i+di]

		if cmd == 'H' || cmd == 'h' {
			ax = coordFloats[di-1]
			if isRelCmd {
				ay = 0
			} else {
				ay = p.y
			}
		} else if cmd == 'V' || cmd == 'v' {
			if isRelCmd {
				ax = 0
			} else {
				ax = p.x
			}
			ay = coordFloats[di-1]
		} else {
			ax = coordFloats[di-2]
			ay = coordFloats[di-1]
		}

		// switch from L to H or V whenever possible
		if cmd == 'L' || cmd == 'l' {
			if isRelCmd {
				if coordFloats[0] == 0 {
					cmd = 'v'
					coords = coords[1:]
					coordFloats = coordFloats[1:]
				} else if coordFloats[1] == 0 {
					cmd = 'h'
					coords = coords[:1]
					coordFloats = coordFloats[:1]
				}
			} else {
				if coordFloats[0] == p.x {
					cmd = 'V'
					coords = coords[1:]
					coordFloats = coordFloats[1:]
				} else if coordFloats[1] == p.y {
					cmd = 'H'
					coords = coords[:1]
					coordFloats = coordFloats[:1]
				}
			}
		}

		// make a current and alternated path with absolute/relative altered
		var curState, altState PathDataState
		curState = p.shortenCurPosInstruction(cmd, coords)
		if isRelCmd {
			altState = p.shortenAltPosInstruction(cmd-'a'+'A', coordFloats, p.x, p.y)
		} else {
			altState = p.shortenAltPosInstruction(cmd-'A'+'a', coordFloats, -p.x, -p.y)
		}

		// choose shortest, relative or absolute path?
		if len(p.altBuffer) < len(p.curBuffer) {
			j += copy(b[j:], p.altBuffer)
			p.state = altState
		} else {
			j += copy(b[j:], p.curBuffer)
			p.state = curState
		}

		if isRelCmd {
			p.x += ax
			p.y += ay
		} else {
			p.x = ax
			p.y = ay
		}
	}
	return j
}

func (p *PathData) shortenCurPosInstruction(cmd byte, coords [][]byte) PathDataState {
	state := p.state
	p.curBuffer = p.curBuffer[:0]
	if cmd != state.cmd && !(state.cmd == 'M' && cmd == 'L' || state.cmd == 'm' && cmd == 'l') {
		p.curBuffer = append(p.curBuffer, cmd)
		state.cmd = cmd
		state.prevDigit = false
		state.prevDigitIsInt = false
	}
	for _, coord := range coords {
		coord := minify.Number(coord, -1)
		if state.prevDigit && (coord[0] >= '0' && coord[0] <= '9' || coord[0] == '.' && state.prevDigitIsInt) {
			p.curBuffer = append(p.curBuffer, ' ')
		}
		state.prevDigit = true
		state.prevDigitIsInt = true
		for _, c := range coord {
			if c == '.' || c == 'e' || c == 'E' {
				state.prevDigitIsInt = false
				break
			}
		}
		p.curBuffer = append(p.curBuffer, coord...)
	}
	return state
}

func (p *PathData) shortenAltPosInstruction(cmd byte, coordFloats []float64, x, y float64) PathDataState {
	state := p.state
	p.altBuffer = p.altBuffer[:0]
	if cmd != state.cmd && !(state.cmd == 'M' && cmd == 'L' || state.cmd == 'm' && cmd == 'l') {
		p.altBuffer = append(p.altBuffer, cmd)
		state.cmd = cmd
		state.prevDigit = false
		state.prevDigitIsInt = false
	}
	for i, f := range coordFloats {
		if cmd == 'L' || cmd == 'l' || cmd == 'C' || cmd == 'c' || cmd == 'S' || cmd == 's' || cmd == 'Q' || cmd == 'q' || cmd == 'T' || cmd == 't' || cmd == 'M' || cmd == 'm' {
			if i%2 == 0 {
				f += x
			} else {
				f += y
			}
		} else if cmd == 'H' || cmd == 'h' {
			f += x
		} else if cmd == 'V' || cmd == 'v' {
			f += y
		} else if cmd == 'A' || cmd == 'a' {
			if i%7 == 5 {
				f += x
			} else if i%7 == 6 {
				f += y
			}
		} else {
			continue
		}

		coord, ok := strconv.AppendFloat(p.coordBuffer[:0], f, p.o.Decimals)
		// if string(coord) != strconvStdlib.FormatFloat(f, 'g', 6, 64) {
		// 	fmt.Println(string(coord), "!=", strconvStdlib.FormatFloat(f, 'g', 6, 64))
		// }
		p.coordBuffer = coord // keep memory
		if !ok {
			p.coordBuffer = strconvStdlib.AppendFloat(p.coordBuffer[:0], f, 'g', p.o.Decimals, 64)
			coord = minify.Number(p.coordBuffer, -1)
		}

		if state.prevDigit && (coord[0] >= '0' && coord[0] <= '9' || coord[0] == '.' && state.prevDigitIsInt) {
			p.altBuffer = append(p.altBuffer, ' ')
		}
		state.prevDigit = true
		state.prevDigitIsInt = true
		for _, c := range coord {
			if c == '.' || c == 'e' || c == 'E' {
				state.prevDigitIsInt = false
				break
			}
		}
		p.altBuffer = append(p.altBuffer, coord...)
	}
	return state
}
<|endoftext|>


<commit_before>package approvals

import (
	"context"
	"encoding/json"
	"errors"
	"sync"
	"sync/atomic"
	"time"

	"github.com/keel-hq/keel/cache"
	"github.com/keel-hq/keel/types"

	log "github.com/sirupsen/logrus"
)

// Manager is used to manage updates
type Manager interface {
	// Subscribe for approval request events, subscriber should provide
	// its name. Indented to be used by extensions that collect
	// approvals
	Subscribe(ctx context.Context) (<-chan *types.Approval, error)

	// SubscribeApproved - is used to get approved events by the manager
	SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error)

	// request approval for deployment/release/etc..
	Create(r *types.Approval) error
	// Update whole approval object
	Update(r *types.Approval) error

	// Increases Approval votes by 1
	Approve(identifier, voter string) (*types.Approval, error)
	// Rejects Approval
	Reject(identifier string) (*types.Approval, error)

	Get(identifier string) (*types.Approval, error)
	List() ([]*types.Approval, error)
	Delete(identifier string) error

	StartExpiryService(ctx context.Context) error
}

// Approvals related errors
var (
	ErrApprovalAlreadyExists = errors.New("approval already exists")
)

// Approvals cache prefix
const (
	ApprovalsPrefix = "approvals"
)

// DefaultManager - default manager implementation
type DefaultManager struct {
	// cache is used to store approvals, key example:
	// approvals/<provider name>/<identifier>
	cache cache.Cache

	// subscriber channels
	channels map[uint64]chan *types.Approval
	index    uint64

	// approved channels
	approvedCh map[uint64]chan *types.Approval

	mu    *sync.Mutex
	subMu *sync.RWMutex
}

// New create new instance of default manager
func New(cache cache.Cache) *DefaultManager {
	man := &DefaultManager{
		cache:      cache,
		channels:   make(map[uint64]chan *types.Approval),
		approvedCh: make(map[uint64]chan *types.Approval),
		index:      0,
		mu:         &sync.Mutex{},
		subMu:      &sync.RWMutex{},
	}

	return man
}

// StartExpiryService - starts approval expiry service which deletes approvals
// that already reached their deadline
func (m *DefaultManager) StartExpiryService(ctx context.Context) error {
	ticker := time.NewTicker(60 * time.Minute)
	defer ticker.Stop()
	err := m.expireEntries()
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("approvals.StartExpiryService: got error while performing initial expired approvals check")
	}

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-ticker.C:
			err := m.expireEntries()
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
				}).Error("approvals.StartExpiryService: got error while performing routinely expired approvals check")
			}
		}
	}
}

func (m *DefaultManager) expireEntries() error {
	approvals, err := m.cache.List(ApprovalsPrefix + "/")
	if err != nil {
		return err
	}

	for k, v := range approvals {
		var approval types.Approval
		err = json.Unmarshal(v, &approval)
		if err != nil {
			log.WithFields(log.Fields{
				"error":      err,
				"identifier": k,
			}).Error("approvals.expireEntries: failed to decode approval into value")
			continue
		}

		if approval.Expired() {
			err = m.Delete(approval.Identifier)
			if err != nil {
				log.WithFields(log.Fields{
					"error":      err,
					"identifier": k,
				}).Error("approvals.expireEntries: failed to delete expired approval")
			}
		}
	}

	return nil
}

// Subscribe - subscribe for approval events
func (m *DefaultManager) Subscribe(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint64(&m.index, 1)
	approvalsCh := make(chan *types.Approval, 10)
	m.channels[index] = approvalsCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.channels, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvalsCh, nil
}

// SubscribeApproved - subscribe for approved update requests
func (m *DefaultManager) SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint64(&m.index, 1)
	approvedCh := make(chan *types.Approval, 10)
	m.approvedCh[index] = approvedCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.approvedCh, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvedCh, nil
}

func (m *DefaultManager) publishRequest(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.channels {
		subscriber <- approval
	}
	return nil
}

func (m *DefaultManager) publishApproved(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.approvedCh {
		subscriber <- approval
	}
	return nil
}

// Create - creates new approval request and publishes to all subscribers
func (m *DefaultManager) Create(r *types.Approval) error {
	_, err := m.Get(r.Identifier)
	if err == nil {
		return ErrApprovalAlreadyExists
	}

	r.CreatedAt = time.Now()
	r.UpdatedAt = time.Now()

	bts, err := json.Marshal(r)
	if err != nil {
		return err
	}

	err = m.cache.Put(getKey(r.Identifier), bts)
	if err != nil {
		return err
	}

	return m.publishRequest(r)
}

// Update - update approval
func (m *DefaultManager) Update(r *types.Approval) error {
	existing, err := m.Get(r.Identifier)
	if err != nil {
		return err
	}

	r.CreatedAt = existing.CreatedAt
	r.UpdatedAt = time.Now()

	bts, err := json.Marshal(r)
	if err != nil {
		return err
	}

	if r.Status() == types.ApprovalStatusApproved {
		err = m.publishApproved(r)
		if err != nil {
			log.WithFields(log.Fields{
				"error":    err,
				"approval": r.Identifier,
				"provider": r.Provider,
			}).Error("approvals.manager: failed to re-submit event after approvals were collected")
		}
	}

	return m.cache.Put(getKey(r.Identifier), bts)
}

// Approve - increase VotesReceived by 1 and returns updated version
func (m *DefaultManager) Approve(identifier, voter string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to get")
		return nil, err
	}

	for _, v := range existing.Voters {
		if v == voter {
			// nothing to do, same voter
			return existing, nil
		}
	}

	existing.Voters = append(existing.Voters, voter)
	existing.VotesReceived++

	err = m.Update(existing)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to update")
		return nil, err
	}

	log.WithFields(log.Fields{
		"identifier": identifier,
	}).Info("approvals.manager: approved")

	return existing, nil
}

// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
// collects required votes
func (m *DefaultManager) Reject(identifier string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		return nil, err
	}

	existing.Rejected = true

	err = m.Update(existing)
	if err != nil {
		return nil, err
	}

	return existing, nil
}

// Get - get specified approval
func (m *DefaultManager) Get(identifier string) (*types.Approval, error) {
	bts, err := m.cache.Get(getKey(identifier))
	if err != nil {
		return nil, err
	}

	var approval types.Approval
	err = json.Unmarshal(bts, &approval)
	return &approval, err
}

// List - list approvals
func (m *DefaultManager) List() ([]*types.Approval, error) {
	bts, err := m.cache.List(ApprovalsPrefix)
	if err != nil {
		return nil, err
	}

	var approvals []*types.Approval
	for _, v := range bts {
		var approval types.Approval
		err = json.Unmarshal(v, &approval)
		if err != nil {
			log.WithFields(log.Fields{
				"error":   err,
				"payload": string(v),
			}).Error("approvals.manager: failed to decode payload")
			continue
		}
		approvals = append(approvals, &approval)
	}
	return approvals, nil
}

// Delete - delete specified approval
func (m *DefaultManager) Delete(identifier string) error {
	return m.cache.Delete(getKey(identifier))
}

func getKey(identifier string) string {
	return ApprovalsPrefix + "/" + identifier
}
<commit_msg>changing to uint32 to help rpi build<commit_after>package approvals

import (
	"context"
	"encoding/json"
	"errors"
	"sync"
	"sync/atomic"
	"time"

	"github.com/keel-hq/keel/cache"
	"github.com/keel-hq/keel/types"

	log "github.com/sirupsen/logrus"
)

// Manager is used to manage updates
type Manager interface {
	// Subscribe for approval request events, subscriber should provide
	// its name. Indented to be used by extensions that collect
	// approvals
	Subscribe(ctx context.Context) (<-chan *types.Approval, error)

	// SubscribeApproved - is used to get approved events by the manager
	SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error)

	// request approval for deployment/release/etc..
	Create(r *types.Approval) error
	// Update whole approval object
	Update(r *types.Approval) error

	// Increases Approval votes by 1
	Approve(identifier, voter string) (*types.Approval, error)
	// Rejects Approval
	Reject(identifier string) (*types.Approval, error)

	Get(identifier string) (*types.Approval, error)
	List() ([]*types.Approval, error)
	Delete(identifier string) error

	StartExpiryService(ctx context.Context) error
}

// Approvals related errors
var (
	ErrApprovalAlreadyExists = errors.New("approval already exists")
)

// Approvals cache prefix
const (
	ApprovalsPrefix = "approvals"
)

// DefaultManager - default manager implementation
type DefaultManager struct {
	// cache is used to store approvals, key example:
	// approvals/<provider name>/<identifier>
	cache cache.Cache

	// subscriber channels
	channels map[uint32]chan *types.Approval
	index    uint32

	// approved channels
	approvedCh map[uint32]chan *types.Approval

	mu    *sync.Mutex
	subMu *sync.RWMutex
}

// New create new instance of default manager
func New(cache cache.Cache) *DefaultManager {
	man := &DefaultManager{
		cache:      cache,
		channels:   make(map[uint32]chan *types.Approval),
		approvedCh: make(map[uint32]chan *types.Approval),
		index:      0,
		mu:         &sync.Mutex{},
		subMu:      &sync.RWMutex{},
	}

	return man
}

// StartExpiryService - starts approval expiry service which deletes approvals
// that already reached their deadline
func (m *DefaultManager) StartExpiryService(ctx context.Context) error {
	ticker := time.NewTicker(60 * time.Minute)
	defer ticker.Stop()
	err := m.expireEntries()
	if err != nil {
		log.WithFields(log.Fields{
			"error": err,
		}).Error("approvals.StartExpiryService: got error while performing initial expired approvals check")
	}

	for {
		select {
		case <-ctx.Done():
			return nil
		case <-ticker.C:
			err := m.expireEntries()
			if err != nil {
				log.WithFields(log.Fields{
					"error": err,
				}).Error("approvals.StartExpiryService: got error while performing routinely expired approvals check")
			}
		}
	}
}

func (m *DefaultManager) expireEntries() error {
	approvals, err := m.cache.List(ApprovalsPrefix + "/")
	if err != nil {
		return err
	}

	for k, v := range approvals {
		var approval types.Approval
		err = json.Unmarshal(v, &approval)
		if err != nil {
			log.WithFields(log.Fields{
				"error":      err,
				"identifier": k,
			}).Error("approvals.expireEntries: failed to decode approval into value")
			continue
		}

		if approval.Expired() {
			err = m.Delete(approval.Identifier)
			if err != nil {
				log.WithFields(log.Fields{
					"error":      err,
					"identifier": k,
				}).Error("approvals.expireEntries: failed to delete expired approval")
			}
		}
	}

	return nil
}

// Subscribe - subscribe for approval events
func (m *DefaultManager) Subscribe(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint32(&m.index, 1)
	approvalsCh := make(chan *types.Approval, 10)
	m.channels[index] = approvalsCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.channels, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvalsCh, nil
}

// SubscribeApproved - subscribe for approved update requests
func (m *DefaultManager) SubscribeApproved(ctx context.Context) (<-chan *types.Approval, error) {
	m.subMu.Lock()
	index := atomic.AddUint32(&m.index, 1)
	approvedCh := make(chan *types.Approval, 10)
	m.approvedCh[index] = approvedCh
	m.subMu.Unlock()

	go func() {
		for {
			select {
			case <-ctx.Done():
				m.subMu.Lock()

				delete(m.approvedCh, index)

				m.subMu.Unlock()
				return
			}
		}
	}()

	return approvedCh, nil
}

func (m *DefaultManager) publishRequest(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.channels {
		subscriber <- approval
	}
	return nil
}

func (m *DefaultManager) publishApproved(approval *types.Approval) error {
	m.subMu.RLock()
	defer m.subMu.RUnlock()

	for _, subscriber := range m.approvedCh {
		subscriber <- approval
	}
	return nil
}

// Create - creates new approval request and publishes to all subscribers
func (m *DefaultManager) Create(r *types.Approval) error {
	_, err := m.Get(r.Identifier)
	if err == nil {
		return ErrApprovalAlreadyExists
	}

	r.CreatedAt = time.Now()
	r.UpdatedAt = time.Now()

	bts, err := json.Marshal(r)
	if err != nil {
		return err
	}

	err = m.cache.Put(getKey(r.Identifier), bts)
	if err != nil {
		return err
	}

	return m.publishRequest(r)
}

// Update - update approval
func (m *DefaultManager) Update(r *types.Approval) error {
	existing, err := m.Get(r.Identifier)
	if err != nil {
		return err
	}

	r.CreatedAt = existing.CreatedAt
	r.UpdatedAt = time.Now()

	bts, err := json.Marshal(r)
	if err != nil {
		return err
	}

	if r.Status() == types.ApprovalStatusApproved {
		err = m.publishApproved(r)
		if err != nil {
			log.WithFields(log.Fields{
				"error":    err,
				"approval": r.Identifier,
				"provider": r.Provider,
			}).Error("approvals.manager: failed to re-submit event after approvals were collected")
		}
	}

	return m.cache.Put(getKey(r.Identifier), bts)
}

// Approve - increase VotesReceived by 1 and returns updated version
func (m *DefaultManager) Approve(identifier, voter string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to get")
		return nil, err
	}

	for _, v := range existing.Voters {
		if v == voter {
			// nothing to do, same voter
			return existing, nil
		}
	}

	existing.Voters = append(existing.Voters, voter)
	existing.VotesReceived++

	err = m.Update(existing)
	if err != nil {
		log.WithFields(log.Fields{
			"identifier": identifier,
			"error":      err,
		}).Error("approvals.manager: failed to update")
		return nil, err
	}

	log.WithFields(log.Fields{
		"identifier": identifier,
	}).Info("approvals.manager: approved")

	return existing, nil
}

// Reject - rejects approval (marks rejected=true), approval will not be valid even if it
// collects required votes
func (m *DefaultManager) Reject(identifier string) (*types.Approval, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	existing, err := m.Get(identifier)
	if err != nil {
		return nil, err
	}

	existing.Rejected = true

	err = m.Update(existing)
	if err != nil {
		return nil, err
	}

	return existing, nil
}

// Get - get specified approval
func (m *DefaultManager) Get(identifier string) (*types.Approval, error) {
	bts, err := m.cache.Get(getKey(identifier))
	if err != nil {
		return nil, err
	}

	var approval types.Approval
	err = json.Unmarshal(bts, &approval)
	return &approval, err
}

// List - list approvals
func (m *DefaultManager) List() ([]*types.Approval, error) {
	bts, err := m.cache.List(ApprovalsPrefix)
	if err != nil {
		return nil, err
	}

	var approvals []*types.Approval
	for _, v := range bts {
		var approval types.Approval
		err = json.Unmarshal(v, &approval)
		if err != nil {
			log.WithFields(log.Fields{
				"error":   err,
				"payload": string(v),
			}).Error("approvals.manager: failed to decode payload")
			continue
		}
		approvals = append(approvals, &approval)
	}
	return approvals, nil
}

// Delete - delete specified approval
func (m *DefaultManager) Delete(identifier string) error {
	return m.cache.Delete(getKey(identifier))
}

func getKey(identifier string) string {
	return ApprovalsPrefix + "/" + identifier
}
<|endoftext|>


<commit_before>package otp

import (
	"testing"
)

func TestInvalidMethod(t *testing.T) {
	key := Key{
		Method: "crypto!",
	}
	v, _ := key.IsValid()
	if v == true {
		t.Fail()
	}
}

func TestInvalidLabel(t *testing.T) {
	key := Key{
		Method: "totp",
		Label:  "t/w",
	}
	v, _ := key.IsValid()
	if v == true {
		t.Fail()
	}
}

func TestTotpString(t *testing.T) {
	key, _ := NewTotp(
		"label",
		"MFRGGZDFMZTWQ2LK",
		"issuer",
		"sha1",
		6,
		30,
	)

	uri := key.String()
	if uri != "otpauth://totp/label?Secret=MFRGGZDFMZTWQ2LK&Issuer=issuer&Algo=SHA1&Digits=6&Period=30" {
		t.Error(uri)
	}
}
<commit_msg>check missing labels<commit_after>package otp

import (
	"testing"
)

func TestInvalidMethod(t *testing.T) {
	key := Key{
		Method: "crypto!",
	}
	v, _ := key.IsValid()
	if v == true {
		t.Fail()
	}
}

func TestMissingLabel(t *testing.T) {
	key := Key{
		Method: "totp",
	}
	v, _ := key.IsValid()
	if v == true {
		t.Fail()
	}
}

func TestInvalidLabel(t *testing.T) {
	key := Key{
		Method: "totp",
		Label:  "t/w",
	}
	v, _ := key.IsValid()
	if v == true {
		t.Fail()
	}
}

func TestTotpString(t *testing.T) {
	key, _ := NewTotp(
		"label",
		"MFRGGZDFMZTWQ2LK",
		"issuer",
		"sha1",
		6,
		30,
	)

	uri := key.String()
	if uri != "otpauth://totp/label?Secret=MFRGGZDFMZTWQ2LK&Issuer=issuer&Algo=SHA1&Digits=6&Period=30" {
		t.Error(uri)
	}
}
<|endoftext|>