<commit_before>// Copyright 2011 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.

package main

import (
	"fmt"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
)

var cmdTool = &Command{
	Run:       runTool,
	UsageLine: "tool command [args...]",
	Short:     "run specified go tool",
	Long: `
Tool runs the go tool command identified by the arguments.
With no arguments it prints the list of known tools.

For more about each tool command, see 'go tool command -h'.
`,
}

var (
	toolGOOS      = runtime.GOOS
	toolGOARCH    = runtime.GOARCH
	toolIsWindows = toolGOOS == "windows"
	toolDir       = filepath.Join(build.Path[0].Path, "bin", "tool")
)

const toolWindowsExtension = ".exe"

func tool(name string) string {
	p := filepath.Join(toolDir, name)
	if toolIsWindows {
		p += toolWindowsExtension
	}
	return p
}

func runTool(cmd *Command, args []string) {
	if len(args) == 0 {
		listTools()
		return
	}
	toolName := args[0]
	// The tool name must be lower-case letters and numbers.
	for _, c := range toolName {
		switch {
		case 'a' <= c && c <= 'z', '0' <= c && c <= '9':
		default:
			fmt.Fprintf(os.Stderr, "go tool: bad tool name %q\n", toolName)
			setExitStatus(2)
			return
		}
	}
	toolPath := tool(toolName)
	// Give a nice message if there is no tool with that name.
	if _, err := os.Stat(toolPath); err != nil {
		fmt.Fprintf(os.Stderr, "go tool: no such tool %q\n", toolName)
		setExitStatus(3)
		return
	}
	toolCmd := &exec.Cmd{
		Path:   toolPath,
		Args:   args,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
	err := toolCmd.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err)
		setExitStatus(1)
		return
	}
}

// listTools prints a list of the available tools in the tools directory.
func listTools() {
	f, err := os.Open(toolDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool: no tool directory: %s\n", err)
		setExitStatus(2)
		return
	}
	defer f.Close()
	names, err := f.Readdirnames(-1)
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool: can't read directory: %s\n", err)
		setExitStatus(2)
		return
	}
	sort.Strings(names)
	for _, name := range names {
		// Unify presentation by going to lower case.
		name = strings.ToLower(name)
		// If it's windows, don't show the .exe suffix.
		if toolIsWindows && strings.HasSuffix(name, toolWindowsExtension) {
			name = name[:len(name)-len(toolWindowsExtension)]
		}
		fmt.Println(name)
	}
}
<commit_msg>cmd/go: let go tool invocation use stdin<commit_after>// Copyright 2011 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.

package main

import (
	"fmt"
	"go/build"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
)

var cmdTool = &Command{
	Run:       runTool,
	UsageLine: "tool command [args...]",
	Short:     "run specified go tool",
	Long: `
Tool runs the go tool command identified by the arguments.
With no arguments it prints the list of known tools.

For more about each tool command, see 'go tool command -h'.
`,
}

var (
	toolGOOS      = runtime.GOOS
	toolGOARCH    = runtime.GOARCH
	toolIsWindows = toolGOOS == "windows"
	toolDir       = filepath.Join(build.Path[0].Path, "bin", "tool")
)

const toolWindowsExtension = ".exe"

func tool(name string) string {
	p := filepath.Join(toolDir, name)
	if toolIsWindows {
		p += toolWindowsExtension
	}
	return p
}

func runTool(cmd *Command, args []string) {
	if len(args) == 0 {
		listTools()
		return
	}
	toolName := args[0]
	// The tool name must be lower-case letters and numbers.
	for _, c := range toolName {
		switch {
		case 'a' <= c && c <= 'z', '0' <= c && c <= '9':
		default:
			fmt.Fprintf(os.Stderr, "go tool: bad tool name %q\n", toolName)
			setExitStatus(2)
			return
		}
	}
	toolPath := tool(toolName)
	// Give a nice message if there is no tool with that name.
	if _, err := os.Stat(toolPath); err != nil {
		fmt.Fprintf(os.Stderr, "go tool: no such tool %q\n", toolName)
		setExitStatus(3)
		return
	}
	toolCmd := &exec.Cmd{
		Path:   toolPath,
		Args:   args,
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
	err := toolCmd.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool %s: %s\n", toolName, err)
		setExitStatus(1)
		return
	}
}

// listTools prints a list of the available tools in the tools directory.
func listTools() {
	f, err := os.Open(toolDir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool: no tool directory: %s\n", err)
		setExitStatus(2)
		return
	}
	defer f.Close()
	names, err := f.Readdirnames(-1)
	if err != nil {
		fmt.Fprintf(os.Stderr, "go tool: can't read directory: %s\n", err)
		setExitStatus(2)
		return
	}
	sort.Strings(names)
	for _, name := range names {
		// Unify presentation by going to lower case.
		name = strings.ToLower(name)
		// If it's windows, don't show the .exe suffix.
		if toolIsWindows && strings.HasSuffix(name, toolWindowsExtension) {
			name = name[:len(name)-len(toolWindowsExtension)]
		}
		fmt.Println(name)
	}
}
<|endoftext|>


<commit_before>package titan

import (
	"fmt"

	"github.com/neptulon/neptulon"
	"github.com/neptulon/neptulon/middleware"
)

func initPrivRoutes(r *middleware.Router, q *Queue) {
	r.Request("msg.echo", middleware.Echo)
	r.Request("msg.send", initSendMsgHandler(q))
	r.Request("msg.recv", initRecvMsgHandler(q))
}

// Allows clients to send messages to each other, online or offline.
func initSendMsgHandler(q *Queue) func(ctx *neptulon.ReqCtx) error {
	type sendMsgReq struct {
		To      string `json:"to"`
		Message string `json:"message"`
	}

	type recvMsgReq struct {
		From    string `json:"from"`
		Message string `json:"message"`
	}

	return func(ctx *neptulon.ReqCtx) error {
		var s sendMsgReq
		ctx.Params(&s)

		uid := ctx.Conn.Session.Get("userid").(string)
		r := recvMsgReq{From: uid, Message: s.Message}
		err := q.AddRequest(s.To, "msg.recv", r, func(ctx *neptulon.ResCtx) error {
			var res string
			ctx.Result(&res)
			if res == "ACK" {
				// todo: send 'delivered' message to sender (as a request?) about this message (or failed, depending on output)
				// todo: q.AddRequest(uid, "msg.delivered", ... // requeue if failed or handle resends automatically in the queue type, which is prefered)
			} else {
				// todo: auto retry or "msg.failed" ?
			}
			return nil
		})

		if err != nil {
			return fmt.Errorf("route: msg.recv: failed to add request to queue with error: %v", err)
		}

		ctx.Res = "ACK"
		return ctx.Next()
	}
}

// Used only for a client to announce its presence.
// If there are any messages meant for this user, they are started to be sent with this call (via the cert-auth middleware).
func initRecvMsgHandler(q *Queue) func(ctx *neptulon.ReqCtx) error {
	return func(ctx *neptulon.ReqCtx) error {
		q.SetConn(ctx.Conn.Session.Get("userid").(string), ctx.Conn.ID)
		ctx.Res = "ACK" // todo: this could rather send the remaining queue size for the client
		return ctx.Next()
	}
}
<commit_msg>reuse Message type declared in client/<commit_after>package titan

import (
	"fmt"

	"github.com/neptulon/neptulon"
	"github.com/neptulon/neptulon/middleware"
	"github.com/titan-x/titan/client"
)

func initPrivRoutes(r *middleware.Router, q *Queue) {
	r.Request("msg.echo", middleware.Echo)
	r.Request("msg.send", initSendMsgHandler(q))
	r.Request("msg.recv", initRecvMsgHandler(q))
}

// Allows clients to send messages to each other, online or offline.
func initSendMsgHandler(q *Queue) func(ctx *neptulon.ReqCtx) error {
	return func(ctx *neptulon.ReqCtx) error {
		var s client.Message
		ctx.Params(&s)

		uid := ctx.Conn.Session.Get("userid").(string)
		r := client.Message{From: uid, Message: s.Message}
		err := q.AddRequest(s.To, "msg.recv", r, func(ctx *neptulon.ResCtx) error {
			var res string
			ctx.Result(&res)
			if res == "ACK" {
				// todo: send 'delivered' message to sender (as a request?) about this message (or failed, depending on output)
				// todo: q.AddRequest(uid, "msg.delivered", ... // requeue if failed or handle resends automatically in the queue type, which is prefered)
			} else {
				// todo: auto retry or "msg.failed" ?
			}
			return nil
		})

		if err != nil {
			return fmt.Errorf("route: msg.recv: failed to add request to queue with error: %v", err)
		}

		ctx.Res = "ACK"
		return ctx.Next()
	}
}

// Used only for a client to announce its presence.
// If there are any messages meant for this user, they are started to be sent with this call (via the cert-auth middleware).
func initRecvMsgHandler(q *Queue) func(ctx *neptulon.ReqCtx) error {
	return func(ctx *neptulon.ReqCtx) error {
		q.SetConn(ctx.Conn.Session.Get("userid").(string), ctx.Conn.ID)
		ctx.Res = "ACK" // todo: this could rather send the remaining queue size for the client
		return ctx.Next()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"time"
)

type Client struct {
	Name            string            //Name of the miner
	IP              string            //Ip to the cgminer including port
	Conn            net.Conn          //Connection made with net.Dial
	RefreshInterval int               //Seconds between fetching information
	MinerInfo       *MinerInformation //Struc to put the answers for the webserver
}

//Main function for fetching information from one client
func rpcClient(name, ip string, refInt int, minerInfo *MinerInformation) {
	//Add everything except the connection
	c := Client{name, ip, nil, refInt, minerInfo}

	fmt.Println(c)

	//Create the connection
	conn, err := net.Dial("tcp", c.IP)

	//Check for errors
	if err != nil {
		log.Println(err)
		return
	}
	//Save it to the struct
	c.Conn = conn

	//Continue asking the miner for the hashrate
	for {
		//Lock because we going to write to the minerInfo
		minerInfo.Mu.Lock()
		//Get the new information
		minerInfo.Hashrate = sendCommand(&c.Conn, "{\"command\":\"summary\"}")
		//fmt.Println("RPC: ", minerInfo.Name)
		//fmt.Println("RPC: ", minerInfo.Hashrate)
		//Now unlock
		minerInfo.Mu.Unlock()
		/*
		 * Note:
		 *
		 * It seems that cgminer close the tcp connection
		 * after each call so we need to reset it for
		 * the next rpc-call
		 */
		conn.Close()
		//Create the new connection
		c.Conn = createConnection(c.IP)

		//Sleep for the a while
		time.Sleep(time.Duration(c.RefreshInterval) * time.Second)
	}
}

// Returns a TCP connection to the ip
func createConnection(ip string) net.Conn {
	conn, err := net.Dial("tcp", ip)

	//Check for errors
	if err != nil {
		log.Println(err)
		return nil
	}

	return conn
}

// Sends a json rpc command to threw the socket and return the answer
func sendCommand(conn *net.Conn, cmd string) string {
	//Write the command to the socket
	fmt.Fprintf(*conn, cmd)
	//Read the response
	status, err := bufio.NewReader(*conn).ReadString('\n')
	//Check for any errors
	if err != nil {
		//Check for errors
		if err == io.EOF {
			/*
			 * Cgminer sends out EOF after each call.
			 * Catch this error because it's not really
			 * an error that crash the program.
			 */

		} else {
			//If the error is not EOF then warn about it
			log.Println("Sending command error: ", err)
		}
	}
	//Return the status we got from the server
	return status
}
<commit_msg>Created struct summary responce<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"time"
)

type Client struct {
	Name            string            //Name of the miner
	IP              string            //Ip to the cgminer including port
	Conn            net.Conn          //Connection made with net.Dial
	RefreshInterval int               //Seconds between fetching information
	MinerInfo       *MinerInformation //Struc to put the answers for the webserver
}

//Main function for fetching information from one client
func rpcClient(name, ip string, refInt int, minerInfo *MinerInformation) {
	//Add everything except the connection
	c := Client{name, ip, nil, refInt, minerInfo}

	fmt.Println(c)

	//Create the connection
	conn, err := net.Dial("tcp", c.IP)

	//Check for errors
	if err != nil {
		log.Println(err)
		return
	}
	//Save it to the struct
	c.Conn = conn

	//Continue asking the miner for the hashrate
	for {
		//Lock because we going to write to the minerInfo
		minerInfo.Mu.Lock()
		//Get the new information
		minerInfo.Hashrate = sendCommand(&c.Conn, "{\"command\":\"summary\"}")
		//fmt.Println("RPC: ", minerInfo.Name)
		//fmt.Println("RPC: ", minerInfo.Hashrate)
		//Now unlock
		minerInfo.Mu.Unlock()
		/*
		 * Note:
		 *
		 * It seems that cgminer close the tcp connection
		 * after each call so we need to reset it for
		 * the next rpc-call
		 */
		conn.Close()
		//Create the new connection
		c.Conn = createConnection(c.IP)

		//Sleep for the a while
		time.Sleep(time.Duration(c.RefreshInterval) * time.Second)
	}
}

// Returns a TCP connection to the ip
func createConnection(ip string) net.Conn {
	conn, err := net.Dial("tcp", ip)

	//Check for errors
	if err != nil {
		log.Println(err)
		return nil
	}

	return conn
}

// Sends a json rpc command to threw the socket and return the answer
func sendCommand(conn *net.Conn, cmd string) string {
	//Write the command to the socket
	fmt.Fprintf(*conn, cmd)
	//Read the response
	status, err := bufio.NewReader(*conn).ReadString('\n')
	//Check for any errors
	if err != nil {
		//Check for errors
		if err == io.EOF {
			/*
			 * Cgminer sends out EOF after each call.
			 * Catch this error because it's not really
			 * an error that crash the program.
			 */

		} else {
			//If the error is not EOF then warn about it
			log.Println("Sending command error: ", err)
		}
	}
	//Return the status we got from the server
	return status
}

/*
 * Bellow here is only structs defined
 * for converting json responces to
 * structs.
 */

//{"command":"summary"}
type SummaryResponce struct {
	Status  []Status
	Summary []struct {
		Elapsed            int
		MHSAv              float64 `json:"MHS av"`
		FoundBlocks        int     `json:"Found blocks"`
		Getworks           int
		Accepted           int
		Rejected           int
		HardwareErrors     int `json:"Hardware Errors"`
		Utility            float64
		Discarded          int
		Stale              int
		GetFailures        int     `json:"Get Failures"`
		LocalWork          int     `json:"Local Work"`
		RemoteFailures     int     `json:"Remote Failures"`
		NetworkBlocks      int     `json:"Network Blocks"`
		TotalMH            float64 `json:"TotalMH"`
		WorkUtility        float64 `json:"Work Utility"`
		DifficultyAccepted int     `json:"Difficulty Accepted"`
		DifficultyRejected int     `json:"Difficulty Rejected"`
		DifficultyStale    int     `json:"Difficulty Stale"`
		BestShare          int     `json:"Best Share"`
	}
	Id int
}

type Status struct {
	Status      string
	When        int
	Code        int
	Msg         string
	Description string
}
<|endoftext|>


<commit_before>package networking_test

import (
	"github.com/cloudfoundry-incubator/garden/api"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"

	"fmt"
	"os/exec"
)

var _ = Describe("IP settings", func() {
	var container api.Container

	BeforeEach(func() {
		client = startGarden()

		var err error

		container, err = client.Create(api.ContainerSpec{Network: "10.3.0.0/24"})
		Ω(err).ShouldNot(HaveOccurred())
	})

	AfterEach(func() {
		err := client.Destroy(container.Handle())
		Ω(err).ShouldNot(HaveOccurred())
	})

	Describe("container's network interface", func() {
		It("has the correct IP address", func() {
			stdout := gbytes.NewBuffer()
			stderr := gbytes.NewBuffer()

			process, err := container.Run(api.ProcessSpec{
				Path: "/sbin/ifconfig",
				Args: []string{containerIfName(container)},
			}, api.ProcessIO{
				Stdout: stdout,
				Stderr: stderr,
			})
			Ω(err).ShouldNot(HaveOccurred())

			rc, err := process.Wait()
			Ω(err).ShouldNot(HaveOccurred())
			Ω(rc).Should(Equal(0))

			Ω(stdout.Contents()).Should(ContainSubstring(" inet addr:10.3.0.1 "))
		})
	})

	Describe("hosts's network interface for a container", func() {
		It("has the correct IP address", func() {
			out, err := exec.Command("/sbin/ifconfig", hostIfName(container)).Output()
			Ω(err).ShouldNot(HaveOccurred())

			Ω(out).Should(ContainSubstring(" inet addr:10.3.0.254 "))
		})
	})

	Describe("the container's network", func() {

		It("is reachable from host", func() {
			info, ierr := container.Info()
			Ω(ierr).ShouldNot(HaveOccurred())

			cmd := exec.Command("/bin/ping", "-c 2 -o -t 2 "+info.HostIP)
			fmt.Println(cmd)

			out, err := cmd.Output()
			Ω(err).ShouldNot(HaveOccurred())

			Ω(out).Should(Equal(""))
		})
	})

})
<commit_msg>Add ping tests<commit_after>package networking_test

import (
	"github.com/cloudfoundry-incubator/garden/api"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"

	"os/exec"
)

var _ = Describe("IP settings", func() {
	var container api.Container

	BeforeEach(func() {
		client = startGarden()

		var err error

		container, err = client.Create(api.ContainerSpec{Network: "10.3.0.0/24"})
		Ω(err).ShouldNot(HaveOccurred())
	})

	AfterEach(func() {
		err := client.Destroy(container.Handle())
		Ω(err).ShouldNot(HaveOccurred())
	})

	Describe("container's network interface", func() {
		It("has the correct IP address", func() {
			stdout := gbytes.NewBuffer()
			stderr := gbytes.NewBuffer()

			process, err := container.Run(api.ProcessSpec{
				Path: "/sbin/ifconfig",
				Args: []string{containerIfName(container)},
			}, api.ProcessIO{
				Stdout: stdout,
				Stderr: stderr,
			})
			Ω(err).ShouldNot(HaveOccurred())

			rc, err := process.Wait()
			Ω(err).ShouldNot(HaveOccurred())
			Ω(rc).Should(Equal(0))

			Ω(stdout.Contents()).Should(ContainSubstring(" inet addr:10.3.0.1 "))
		})
	})

	Describe("hosts's network interface for a container", func() {
		It("has the correct IP address", func() {
			out, err := exec.Command("/sbin/ifconfig", hostIfName(container)).Output()
			Ω(err).ShouldNot(HaveOccurred())

			Ω(out).Should(ContainSubstring(" inet addr:10.3.0.254 "))
		})
	})

	Describe("the container's network", func() {
		It("is reachable from the host", func() {
			info, ierr := container.Info()
			Ω(ierr).ShouldNot(HaveOccurred())

			out, err := exec.Command("/bin/ping", "-c 2", info.ContainerIP).Output()
			Ω(err).ShouldNot(HaveOccurred())

			Ω(out).Should(ContainSubstring("0% packet loss"))
		})
	})

	Describe("host's network", func() {
		It("is reachable from inside the container", func() {
			info, ierr := container.Info()
			Ω(ierr).ShouldNot(HaveOccurred())

			stdout := gbytes.NewBuffer()
			stderr := gbytes.NewBuffer()

			process, err := container.Run(api.ProcessSpec{
				Path: "/bin/ping",
				Args: []string{"-c", "2", info.HostIP},
			}, api.ProcessIO{
				Stdout: stdout,
				Stderr: stderr,
			})
			Ω(err).ShouldNot(HaveOccurred())

			rc, err := process.Wait()
			Ω(err).ShouldNot(HaveOccurred())
			Ω(rc).Should(Equal(0))

			Ω(stdout.Contents()).Should(ContainSubstring("0% packet loss"))
		})
	})

})
<|endoftext|>


<commit_before>package provider

import (
	"context"
	"fmt"
	"strings"

	petname "github.com/dustinkirkland/golang-petname"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourcePet() *schema.Resource {
	// This is necessary to ensure each call to petname is properly randomised:
	// the library uses `rand.Intn()` and does NOT seed `rand.Seed()` by default,
	// so this call takes care of that.
	petname.NonDeterministicMode()

	return &schema.Resource{
		Description: "The resource `random_pet` generates random pet names that are intended to be used as " +
			"unique identifiers for other resources.\n" +
			"\n" +
			"This resource can be used in conjunction with resources that have the `create_before_destroy` " +
			"lifecycle flag set, to avoid conflicts with unique names during the brief period where both the old " +
			"and new resources exist concurrently.",
		CreateContext: CreatePet,
		ReadContext:   schema.NoopContext,
		DeleteContext: RemoveResourceFromState,

		Schema: map[string]*schema.Schema{
			"keepers": {
				Description: "Arbitrary map of values that, when changed, will trigger recreation of " +
					"resource. See [the main provider documentation](../index.html) for more information.",
				Type:     schema.TypeMap,
				Optional: true,
				ForceNew: true,
			},

			"length": {
				Description: "The length (in words) of the pet name. Defaults to 2",
				Type:        schema.TypeInt,
				Optional:    true,
				Default:     2,
				ForceNew:    true,
			},

			"prefix": {
				Description: "A string to prefix the name with.",
				Type:        schema.TypeString,
				Optional:    true,
				ForceNew:    true,
			},

			"separator": {
				Description: "The character to separate words in the pet name. Defaults to \"-\"",
				Type:        schema.TypeString,
				Optional:    true,
				Default:     "-",
				ForceNew:    true,
			},

			"id": {
				Description: "The random pet name",
				Type:        schema.TypeString,
				Computed:    true,
			},
		},
	}
}

func CreatePet(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	length := d.Get("length").(int)
	separator := d.Get("separator").(string)
	prefix := d.Get("prefix").(string)

	pet := strings.ToLower(petname.Generate(length, separator))

	if prefix != "" {
		pet = fmt.Sprintf("%s%s%s", prefix, separator, pet)
	}

	d.SetId(pet)

	return nil
}
<commit_msg>Removing dead code (#177)<commit_after><|endoftext|>


<commit_before>// Copyright 2019 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.

// Package sample provides functionality for generating sample values of
// the types contained in the internal package.
package sample

import (
	"fmt"
	"math"
	"path"
	"strings"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/google/licensecheck"
	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/licenses"
	"golang.org/x/pkgsite/internal/source"
	"golang.org/x/pkgsite/internal/stdlib"
	"golang.org/x/pkgsite/internal/version"
)

// These sample values can be used to construct test cases.
var (
	ModulePath      = "github.com/valid_module_name"
	RepositoryURL   = "https://github.com/valid_module_name"
	VersionString   = "v1.0.0"
	CommitTime      = NowTruncated()
	LicenseMetadata = []*licenses.Metadata{
		{
			Types:    []string{"MIT"},
			FilePath: "LICENSE",
			Coverage: licensecheck.Coverage{
				Percent: 100,
				Match:   []licensecheck.Match{{Name: "MIT", Type: licensecheck.MIT, Percent: 100}},
			},
		},
	}
	Licenses = []*licenses.License{
		{Metadata: LicenseMetadata[0], Contents: []byte(`Lorem Ipsum`)},
	}
	DocumentationHTML = "This is the documentation HTML"
	PackageName       = "foo"
	Suffix            = "foo"
	PackagePath       = path.Join(ModulePath, Suffix)
	V1Path            = PackagePath
	Imports           = []string{"path/to/bar", "fmt"}
	Synopsis          = "This is a package synopsis"
	ReadmeFilePath    = "README.md"
	ReadmeContents    = "readme"
	GOOS              = "linux"
	GOARCH            = "amd64"
)

// LicenseCmpOpts are options to use when comparing licenses with the cmp package.
var LicenseCmpOpts = []cmp.Option{
	cmp.Comparer(coveragePercentEqual),
	cmpopts.IgnoreFields(licensecheck.Match{}, "Start", "End"),
}

// coveragePercentEqual considers two floats the same if they are within 4
// percentage points, and both are on the same side of 90% (our threshold).
func coveragePercentEqual(a, b float64) bool {
	if (a >= 90) != (b >= 90) {
		return false
	}
	return math.Abs(a-b) <= 4
}

// NowTruncated returns time.Now() truncated to Microsecond precision.
//
// This makes it easier to work with timestamps in PostgreSQL, which have
// Microsecond precision:
//   https://www.postgresql.org/docs/9.1/datatype-datetime.html
func NowTruncated() time.Time {
	return time.Now().Truncate(time.Microsecond)
}

// Package constructs a package with the given module path and suffix.
//
// If modulePath is the standard library, the package path is the
// suffix, which must not be empty. Otherwise, the package path
// is the concatenation of modulePath and suffix.
//
// The package name is last component of the package path.
func Package(modulePath, suffix string) *internal.Package {
	pkgPath := suffix
	if modulePath != stdlib.ModulePath {
		pkgPath = path.Join(modulePath, suffix)
	}
	return &internal.Package{
		Name:              path.Base(pkgPath),
		Path:              pkgPath,
		V1Path:            internal.V1Path(modulePath, suffix),
		Synopsis:          Synopsis,
		IsRedistributable: true,
		Licenses:          LicenseMetadata,
		DocumentationHTML: DocumentationHTML,
		Imports:           Imports,
		GOOS:              GOOS,
		GOARCH:            GOARCH,
	}
}

func ModuleInfo(modulePath, versionString string) *internal.ModuleInfo {
	vtype, err := version.ParseType(versionString)
	if err != nil {
		panic(err)
	}
	mi := ModuleInfoReleaseType(modulePath, versionString)
	mi.VersionType = vtype
	return mi
}

// We shouldn't need this, but some code (notably frontend/directory_test.go) creates
// ModuleInfos with "latest" for version, which should not be valid.
func ModuleInfoReleaseType(modulePath, versionString string) *internal.ModuleInfo {
	return &internal.ModuleInfo{
		ModulePath:        modulePath,
		Version:           versionString,
		ReadmeFilePath:    ReadmeFilePath,
		ReadmeContents:    ReadmeContents,
		CommitTime:        CommitTime,
		VersionType:       version.TypeRelease,
		SourceInfo:        source.NewGitHubInfo(RepositoryURL, "", ""),
		IsRedistributable: true,
		HasGoMod:          true,
	}
}

func DefaultModule() *internal.Module {
	return AddPackage(
		Module(ModulePath, VersionString),
		Package(ModulePath, Suffix))
}

// Module creates a Module with the given path and version.
// The list of suffixes is used to create Packages within the module.
func Module(modulePath, version string, suffixes ...string) *internal.Module {
	mi := ModuleInfo(modulePath, version)
	m := &internal.Module{
		ModuleInfo: *mi,
		Packages:   nil,
		Licenses:   Licenses,
		Directories: []*internal.DirectoryNew{
			DirectoryNewForModuleRoot(mi, LicenseMetadata),
		},
	}
	for _, s := range suffixes {
		AddPackage(m, Package(modulePath, s))
	}
	return m
}

func AddPackage(m *internal.Module, p *internal.Package) *internal.Module {
	if m.ModulePath != stdlib.ModulePath && !strings.HasPrefix(p.Path, m.ModulePath) {
		panic(fmt.Sprintf("package path %q not a prefix of module path %q",
			p.Path, m.ModulePath))
	}
	m.Packages = append(m.Packages, p)
	m.Directories = append(m.Directories, DirectoryNewForPackage(p))
	minLen := len(m.ModulePath)
	if m.ModulePath == stdlib.ModulePath {
		minLen = 1
	}
	for pth := p.Path; len(pth) > minLen; pth = path.Dir(pth) {
		found := false
		for _, d := range m.Directories {
			if d.Path == pth {
				found = true
				break
			}
		}
		if !found {
			m.Directories = append(m.Directories, DirectoryNewEmpty(pth))
		}
	}
	return m
}

func DirectoryNewEmpty(path string) *internal.DirectoryNew {
	return &internal.DirectoryNew{
		Path:              path,
		IsRedistributable: true,
		Licenses:          LicenseMetadata,
		V1Path:            path,
	}
}

func DirectoryNewForModuleRoot(m *internal.ModuleInfo, licenses []*licenses.Metadata) *internal.DirectoryNew {
	d := &internal.DirectoryNew{
		Path:              m.ModulePath,
		IsRedistributable: m.IsRedistributable,
		Licenses:          licenses,
		V1Path:            internal.SeriesPathForModule(m.ModulePath),
	}
	if m.ReadmeFilePath != "" {
		d.Readme = &internal.Readme{
			Filepath: m.ReadmeFilePath,
			Contents: m.ReadmeContents,
		}
	}
	return d
}

func DirectoryNewForPackage(pkg *internal.Package) *internal.DirectoryNew {
	return &internal.DirectoryNew{
		Path:              pkg.Path,
		IsRedistributable: pkg.IsRedistributable,
		Licenses:          pkg.Licenses,
		V1Path:            pkg.V1Path,
		Package: &internal.PackageNew{
			Name:    pkg.Name,
			Path:    pkg.Path,
			Imports: pkg.Imports,
			Documentation: &internal.Documentation{
				Synopsis: pkg.Synopsis,
				HTML:     pkg.DocumentationHTML,
				GOOS:     pkg.GOOS,
				GOARCH:   pkg.GOARCH,
			},
		},
	}
}
<commit_msg>internal/sample: fix SourceInfo<commit_after>// Copyright 2019 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.

// Package sample provides functionality for generating sample values of
// the types contained in the internal package.
package sample

import (
	"fmt"
	"math"
	"path"
	"strings"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/google/licensecheck"
	"golang.org/x/pkgsite/internal"
	"golang.org/x/pkgsite/internal/licenses"
	"golang.org/x/pkgsite/internal/source"
	"golang.org/x/pkgsite/internal/stdlib"
	"golang.org/x/pkgsite/internal/version"
)

// These sample values can be used to construct test cases.
var (
	ModulePath      = "github.com/valid_module_name"
	RepositoryURL   = "https://github.com/valid_module_name"
	VersionString   = "v1.0.0"
	CommitTime      = NowTruncated()
	LicenseMetadata = []*licenses.Metadata{
		{
			Types:    []string{"MIT"},
			FilePath: "LICENSE",
			Coverage: licensecheck.Coverage{
				Percent: 100,
				Match:   []licensecheck.Match{{Name: "MIT", Type: licensecheck.MIT, Percent: 100}},
			},
		},
	}
	Licenses = []*licenses.License{
		{Metadata: LicenseMetadata[0], Contents: []byte(`Lorem Ipsum`)},
	}
	DocumentationHTML = "This is the documentation HTML"
	PackageName       = "foo"
	Suffix            = "foo"
	PackagePath       = path.Join(ModulePath, Suffix)
	V1Path            = PackagePath
	Imports           = []string{"path/to/bar", "fmt"}
	Synopsis          = "This is a package synopsis"
	ReadmeFilePath    = "README.md"
	ReadmeContents    = "readme"
	GOOS              = "linux"
	GOARCH            = "amd64"
)

// LicenseCmpOpts are options to use when comparing licenses with the cmp package.
var LicenseCmpOpts = []cmp.Option{
	cmp.Comparer(coveragePercentEqual),
	cmpopts.IgnoreFields(licensecheck.Match{}, "Start", "End"),
}

// coveragePercentEqual considers two floats the same if they are within 4
// percentage points, and both are on the same side of 90% (our threshold).
func coveragePercentEqual(a, b float64) bool {
	if (a >= 90) != (b >= 90) {
		return false
	}
	return math.Abs(a-b) <= 4
}

// NowTruncated returns time.Now() truncated to Microsecond precision.
//
// This makes it easier to work with timestamps in PostgreSQL, which have
// Microsecond precision:
//   https://www.postgresql.org/docs/9.1/datatype-datetime.html
func NowTruncated() time.Time {
	return time.Now().Truncate(time.Microsecond)
}

// Package constructs a package with the given module path and suffix.
//
// If modulePath is the standard library, the package path is the
// suffix, which must not be empty. Otherwise, the package path
// is the concatenation of modulePath and suffix.
//
// The package name is last component of the package path.
func Package(modulePath, suffix string) *internal.Package {
	pkgPath := suffix
	if modulePath != stdlib.ModulePath {
		pkgPath = path.Join(modulePath, suffix)
	}
	return &internal.Package{
		Name:              path.Base(pkgPath),
		Path:              pkgPath,
		V1Path:            internal.V1Path(modulePath, suffix),
		Synopsis:          Synopsis,
		IsRedistributable: true,
		Licenses:          LicenseMetadata,
		DocumentationHTML: DocumentationHTML,
		Imports:           Imports,
		GOOS:              GOOS,
		GOARCH:            GOARCH,
	}
}

func ModuleInfo(modulePath, versionString string) *internal.ModuleInfo {
	vtype, err := version.ParseType(versionString)
	if err != nil {
		panic(err)
	}
	mi := ModuleInfoReleaseType(modulePath, versionString)
	mi.VersionType = vtype
	return mi
}

// We shouldn't need this, but some code (notably frontend/directory_test.go) creates
// ModuleInfos with "latest" for version, which should not be valid.
func ModuleInfoReleaseType(modulePath, versionString string) *internal.ModuleInfo {
	return &internal.ModuleInfo{
		ModulePath:     modulePath,
		Version:        versionString,
		ReadmeFilePath: ReadmeFilePath,
		ReadmeContents: ReadmeContents,
		CommitTime:     CommitTime,
		VersionType:    version.TypeRelease,
		// Assume the module path is a GitHub-like repo name.
		SourceInfo:        source.NewGitHubInfo("https://"+modulePath, "", versionString),
		IsRedistributable: true,
		HasGoMod:          true,
	}
}

func DefaultModule() *internal.Module {
	return AddPackage(
		Module(ModulePath, VersionString),
		Package(ModulePath, Suffix))
}

// Module creates a Module with the given path and version.
// The list of suffixes is used to create Packages within the module.
func Module(modulePath, version string, suffixes ...string) *internal.Module {
	mi := ModuleInfo(modulePath, version)
	m := &internal.Module{
		ModuleInfo: *mi,
		Packages:   nil,
		Licenses:   Licenses,
		Directories: []*internal.DirectoryNew{
			DirectoryNewForModuleRoot(mi, LicenseMetadata),
		},
	}
	for _, s := range suffixes {
		AddPackage(m, Package(modulePath, s))
	}
	return m
}

func AddPackage(m *internal.Module, p *internal.Package) *internal.Module {
	if m.ModulePath != stdlib.ModulePath && !strings.HasPrefix(p.Path, m.ModulePath) {
		panic(fmt.Sprintf("package path %q not a prefix of module path %q",
			p.Path, m.ModulePath))
	}
	m.Packages = append(m.Packages, p)
	m.Directories = append(m.Directories, DirectoryNewForPackage(p))
	minLen := len(m.ModulePath)
	if m.ModulePath == stdlib.ModulePath {
		minLen = 1
	}
	for pth := p.Path; len(pth) > minLen; pth = path.Dir(pth) {
		found := false
		for _, d := range m.Directories {
			if d.Path == pth {
				found = true
				break
			}
		}
		if !found {
			m.Directories = append(m.Directories, DirectoryNewEmpty(pth))
		}
	}
	return m
}

func DirectoryNewEmpty(path string) *internal.DirectoryNew {
	return &internal.DirectoryNew{
		Path:              path,
		IsRedistributable: true,
		Licenses:          LicenseMetadata,
		V1Path:            path,
	}
}

func DirectoryNewForModuleRoot(m *internal.ModuleInfo, licenses []*licenses.Metadata) *internal.DirectoryNew {
	d := &internal.DirectoryNew{
		Path:              m.ModulePath,
		IsRedistributable: m.IsRedistributable,
		Licenses:          licenses,
		V1Path:            internal.SeriesPathForModule(m.ModulePath),
	}
	if m.ReadmeFilePath != "" {
		d.Readme = &internal.Readme{
			Filepath: m.ReadmeFilePath,
			Contents: m.ReadmeContents,
		}
	}
	return d
}

func DirectoryNewForPackage(pkg *internal.Package) *internal.DirectoryNew {
	return &internal.DirectoryNew{
		Path:              pkg.Path,
		IsRedistributable: pkg.IsRedistributable,
		Licenses:          pkg.Licenses,
		V1Path:            pkg.V1Path,
		Package: &internal.PackageNew{
			Name:    pkg.Name,
			Path:    pkg.Path,
			Imports: pkg.Imports,
			Documentation: &internal.Documentation{
				Synopsis: pkg.Synopsis,
				HTML:     pkg.DocumentationHTML,
				GOOS:     pkg.GOOS,
				GOARCH:   pkg.GOARCH,
			},
		},
	}
}
<|endoftext|>


<commit_before>package image

import (
	"errors"
	"github.com/watermint/essentials/egraphic/ecolor"
	"github.com/watermint/essentials/egraphic/edraw"
	"github.com/watermint/essentials/egraphic/egeom"
	"github.com/watermint/essentials/egraphic/eimage"
	"github.com/watermint/essentials/egraphic/etext"
	"github.com/watermint/toolbox/essentials/model/mo_path"
	"github.com/watermint/toolbox/essentials/model/mo_string"
	"github.com/watermint/toolbox/infra/control/app_control"
	"github.com/watermint/toolbox/infra/recipe/rc_exec"
	"github.com/watermint/toolbox/infra/recipe/rc_recipe"
	"github.com/watermint/toolbox/infra/ui/app_msg"
	"github.com/watermint/toolbox/quality/infra/qt_file"
	"io/ioutil"
	"os"
	"path/filepath"
)

type Placeholder struct {
	Width                 int
	Height                int
	Color                 string
	Text                  mo_string.OptionalString
	TextColor             string
	TextPosition          string
	TextAlign             mo_string.SelectString
	FontSize              int
	FontPath              mo_string.OptionalString
	Path                  mo_path.FileSystemPath
	ErrorFontPathRequired app_msg.Message
	ErrorCantLoadFont     app_msg.Message
}

func (z *Placeholder) Preset() {
	z.Width = 640
	z.Height = 400
	z.Color = "white"
	z.TextColor = "black"
	z.FontSize = 12
	z.TextPosition = "center"
	z.TextAlign.SetOptions(
		"left",
		"left", "center", "right",
	)
}

func (z *Placeholder) Exec(c app_control.Control) error {
	ui := c.UI()
	bgColor, oc := ecolor.ParseColor(z.Color)
	if oc.IsError() {
		return oc.Cause()
	}

	img := eimage.NewRgba(z.Width, z.Height)
	imgDraw := edraw.NewImageDrawer(img)
	imgDraw.FillRectangle(img.Bounds(), bgColor)

	if z.Text.IsExists() {
		if !z.FontPath.IsExists() {
			ui.Error(z.ErrorFontPathRequired)
			return errors.New("font path required to draw text")
		}
		fontData, err := ioutil.ReadFile(z.FontPath.Value())
		if err != nil {
			ui.Error(z.ErrorCantLoadFont.With("Path", z.FontPath.Value()).With("Error", err))
			return err
		}
		ttf, oc := etext.NewTrueTypeParse(fontData)
		if oc.IsError() {
			ui.Error(z.ErrorCantLoadFont.With("Path", z.FontPath.Value()).With("Error", oc.Cause()))
			return oc.Cause()
		}

		txtColor, oc := ecolor.ParseColor(z.TextColor)
		if oc.IsError() {
			return oc.Cause()
		}
		var txtAlign etext.Alignment
		switch z.TextAlign.Value() {
		case "center":
			txtAlign = etext.AlignCenter
		case "right":
			txtAlign = etext.AlignRight
		default:
			txtAlign = etext.AlignLeft
		}
		txtStyle := etext.NewStyle(ttf.WithSize(z.FontSize), txtColor).WithAlignment(txtAlign)
		txtPos, oc := egeom.ParsePosition(z.TextPosition)
		if oc.IsError() {
			return oc.Cause()
		}

		imgDraw.DrawString(
			txtPos.Locate(
				img.Bounds(),
				txtStyle.Bound(z.Text.Value()),
				egeom.NewPaddingFixed(z.FontSize, z.FontSize),
			),
			z.Text.Value(),
			txtStyle,
		)
	}

	if oc := img.ExportTo(eimage.FormatPng, z.Path.Path()); oc.IsError() {
		return oc.Cause()
	}
	return nil
}

func (z *Placeholder) Test(c app_control.Control) error {
	p, err := qt_file.MakeTestFolder("placeholder", false)
	if err != nil {
		return err
	}
	defer func() {
		_ = os.RemoveAll(p)
	}()

	return rc_exec.Exec(c, &Placeholder{}, func(r rc_recipe.Recipe) {
		m := r.(*Placeholder)
		m.Path = mo_path.NewFileSystemPath(filepath.Join(p, "test.png"))
		m.Text = mo_string.NewOptional("watermint toolbox")
		m.Color = "marker(b18)"
		m.TextColor = "marker(w00)"
		m.TextPosition = "top-right"
		m.TextAlign.SetSelect("right")
	})
}
<commit_msg>#575 : replace open-golang<commit_after>package image

import (
	"errors"
	"github.com/watermint/essentials/egraphic/ecolor"
	"github.com/watermint/essentials/egraphic/edraw"
	"github.com/watermint/essentials/egraphic/egeom"
	"github.com/watermint/essentials/egraphic/eimage"
	"github.com/watermint/essentials/egraphic/etext"
	"github.com/watermint/toolbox/essentials/model/mo_path"
	"github.com/watermint/toolbox/essentials/model/mo_string"
	"github.com/watermint/toolbox/infra/control/app_control"
	"github.com/watermint/toolbox/infra/recipe/rc_exec"
	"github.com/watermint/toolbox/infra/recipe/rc_recipe"
	"github.com/watermint/toolbox/infra/ui/app_msg"
	"github.com/watermint/toolbox/quality/infra/qt_file"
	"io/ioutil"
	"os"
	"path/filepath"
)

type Placeholder struct {
	Width                 int
	Height                int
	Color                 string
	Text                  mo_string.OptionalString
	TextColor             string
	TextPosition          string
	TextAlign             mo_string.SelectString
	FontSize              int
	FontPath              mo_string.OptionalString
	Path                  mo_path.FileSystemPath
	ErrorFontPathRequired app_msg.Message
	ErrorCantLoadFont     app_msg.Message
}

func (z *Placeholder) Preset() {
	z.Width = 640
	z.Height = 400
	z.Color = "white"
	z.TextColor = "black"
	z.FontSize = 12
	z.TextPosition = "center"
	z.TextAlign.SetOptions(
		"left",
		"left", "center", "right",
	)
}

func (z *Placeholder) Exec(c app_control.Control) error {
	ui := c.UI()
	bgColor, oc := ecolor.ParseColor(z.Color)
	if oc.IsError() {
		return oc.Cause()
	}

	img := eimage.NewRgba(z.Width, z.Height)
	imgDraw := edraw.NewImageDrawer(img)
	imgDraw.FillRectangle(img.Bounds(), bgColor)

	if z.Text.IsExists() {
		if !z.FontPath.IsExists() {
			ui.Error(z.ErrorFontPathRequired)
			return errors.New("font path required to draw text")
		}
		fontData, err := ioutil.ReadFile(z.FontPath.Value())
		if err != nil {
			ui.Error(z.ErrorCantLoadFont.With("Path", z.FontPath.Value()).With("Error", err))
			return err
		}
		ttf, oc := etext.NewTrueTypeParse(fontData)
		if oc.IsError() {
			ui.Error(z.ErrorCantLoadFont.With("Path", z.FontPath.Value()).With("Error", oc.Cause()))
			return oc.Cause()
		}

		txtColor, oc := ecolor.ParseColor(z.TextColor)
		if oc.IsError() {
			return oc.Cause()
		}
		var txtAlign etext.Alignment
		switch z.TextAlign.Value() {
		case "center":
			txtAlign = etext.AlignCenter
		case "right":
			txtAlign = etext.AlignRight
		default:
			txtAlign = etext.AlignLeft
		}
		txtStyle := etext.NewStyle(ttf.WithSize(z.FontSize), txtColor).WithAlignment(txtAlign)
		txtPos, oc := egeom.ParsePosition(z.TextPosition)
		if oc.IsError() {
			return oc.Cause()
		}

		imgDraw.DrawString(
			txtPos.Locate(
				img.Bounds(),
				txtStyle.Bound(z.Text.Value()),
				egeom.NewPaddingFixed(z.FontSize, z.FontSize),
			),
			z.Text.Value(),
			txtStyle,
		)
	}

	if oc := img.ExportTo(eimage.FormatPng, z.Path.Path()); oc.IsError() {
		return oc.Cause()
	}
	return nil
}

func (z *Placeholder) Test(c app_control.Control) error {
	p, err := qt_file.MakeTestFolder("placeholder", false)
	if err != nil {
		return err
	}
	defer func() {
		_ = os.RemoveAll(p)
	}()

	return rc_exec.Exec(c, &Placeholder{}, func(r rc_recipe.Recipe) {
		m := r.(*Placeholder)
		m.Path = mo_path.NewFileSystemPath(filepath.Join(p, "test.png"))
		//		m.Text = mo_string.NewOptional("watermint toolbox")
		m.Color = "marker(b18)"
		m.TextColor = "marker(w00)"
		m.TextPosition = "top-right"
		m.TextAlign.SetSelect("right")
	})
}
<|endoftext|>


<commit_before>package reminderplugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/iopred/bruxism"
)

// A Reminder holds data about a specific reminder.
type Reminder struct {
	StartTime time.Time
	Time      time.Time
	Requester string
	Target    string
	Message   string
	IsPrivate bool
}

// ReminderPlugin is a plugin that reminds users.
type ReminderPlugin struct {
	sync.RWMutex
	bruxism.SimplePlugin
	bot       *bruxism.Bot
	Reminders []*Reminder
}

var randomTimes = []string{
	"1 minute",
	"10 minutes",
	"1 hour",
	"4 hours",
	"tomorrow",
	"next week",
}

var randomMessages = []string{
	"walk the dog",
	"take pizza out of the oven",
	"check my email",
	"feed the baby",
	"play some quake",
}

func (p *ReminderPlugin) random(list []string) string {
	return list[rand.Intn(len(list))]
}

func (p *ReminderPlugin) randomReminder(service bruxism.Service) string {
	ticks := ""
	if service.Name() == bruxism.DiscordServiceName {
		ticks = "`"
	}

	return fmt.Sprintf("%s%sreminder %s %s%s", ticks, service.CommandPrefix(), p.random(randomTimes), p.random(randomMessages), ticks)
}

func (p *ReminderPlugin) helpFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	help := []string{
		bruxism.CommandHelp(service, "reminder", "<time> <reminder>", "Sets a reminder that is sent after the provided time.")[0],
	}
	if detailed {
		help = append(help, []string{
			"Examples: ",
			p.randomReminder(service),
			p.randomReminder(service),
		}...)
	}
	return help
}

func (p *ReminderPlugin) parseReminder(parts []string) (time.Time, string, error) {
	if parts[0] == "tomorrow" {
		return time.Now().Add(1 * time.Hour * 24), strings.Join(parts[1:], " "), nil
	}

	if parts[0] == "next" {
		switch parts[1] {
		case "week":
			return time.Now().Add(1 * time.Hour * 24 * 7), strings.Join(parts[2:], " "), nil
		case "month":
			return time.Now().Add(1 * time.Hour * 24 * 7 * 4), strings.Join(parts[2:], " "), nil
		case "year":
			return time.Now().Add(1 * time.Hour * 24 * 365), strings.Join(parts[2:], " "), nil
		default:
			return time.Time{}, "", errors.New("Invalid next.")
		}
	}

	i, err := strconv.Atoi(parts[0])
	if err != nil {
		return time.Time{}, "", err
	}

	switch {
	case strings.HasPrefix(parts[1], "sec"):
		return time.Now().Add(time.Duration(i) * time.Second), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "min"):
		return time.Now().Add(time.Duration(i) * time.Minute), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "hour"):
		return time.Now().Add(time.Duration(i) * time.Hour), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "day"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "week"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24 * 7), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "month"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24 * 7 * 4), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "year"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24 * 365), strings.Join(parts[2:], " "), nil
	}

	return time.Time{}, "", errors.New("Invalid string.")
}

// AddReminder adds a reminder.
func (p *ReminderPlugin) AddReminder(reminder *Reminder) error {
	p.Lock()
	defer p.Unlock()

	i := 0
	for _, r := range p.Reminders {
		if r.Requester == reminder.Requester {
			i++
			if i > 5 {
				return errors.New("You have too many reminders already.")
			}
		}
	}

	i = 0
	for _, r := range p.Reminders {
		if r.Time.After(reminder.Time) {
			break
		}
		i++
	}

	p.Reminders = append(p.Reminders, reminder)
	copy(p.Reminders[i+1:], p.Reminders[i:])
	p.Reminders[i] = reminder

	return nil
}

func (p *ReminderPlugin) messageFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if !service.IsMe(message) {
		if bruxism.MatchesCommand(service, "remind", message) {
			_, parts := bruxism.ParseCommand(service, message)

			if len(parts) < 2 {
				service.SendMessage(message.Channel(), fmt.Sprintf("Invalid reminder, no time or message. eg: %s", p.randomReminder(service)))
				return
			}

			t, r, err := p.parseReminder(parts)

			now := time.Now()

			if err != nil || t.Before(now) || t.After(now.Add(time.Hour*24*365+time.Hour)) {
				service.SendMessage(message.Channel(), fmt.Sprintf("Invalid time. eg: %s", strings.Join(randomTimes, ", ")))
				return
			}

			if r == "" {
				service.SendMessage(message.Channel(), fmt.Sprintf("Invalid reminder, no message. eg: %s", p.randomReminder(service)))
				return
			}

			requester := message.UserName()
			if service.Name() == bruxism.DiscordServiceName {
				requester = fmt.Sprintf("<@%s>", message.UserID())
			}

			err = p.AddReminder(&Reminder{
				StartTime: now,
				Time:      t,
				Requester: requester,
				Target:    message.Channel(),
				Message:   r,
				IsPrivate: service.IsPrivate(message),
			})
			if err != nil {
				service.SendMessage(message.Channel(), err.Error())
				return
			}

			service.SendMessage(message.Channel(), fmt.Sprintf("Reminder set for %s.", humanize.Time(t)))
		}
	}
}

// SendReminder sends a reminder.
func (p *ReminderPlugin) SendReminder(service bruxism.Service, reminder *Reminder) {
	if reminder.IsPrivate {
		service.SendMessage(reminder.Target, fmt.Sprintf("%s you set a reminder: %s", humanize.Time(reminder.StartTime), reminder.Message))
	} else {
		service.SendMessage(reminder.Target, fmt.Sprintf("%s %s set a reminder: %s", humanize.Time(reminder.StartTime), reminder.Requester, reminder.Message))
	}
}

// Run will block until a reminder needs to be fired and then fire it.
func (p *ReminderPlugin) Run(bot *bruxism.Bot, service bruxism.Service) {
	for {
		p.RLock()

		if len(p.Reminders) > 0 {
			reminder := p.Reminders[0]
			if time.Now().After(reminder.Time) {
				p.SendReminder(service, reminder)
				p.Reminders = p.Reminders[1:]
				p.RUnlock()
				continue
			}
		}

		p.RUnlock()
		time.Sleep(500 * time.Millisecond)
	}
}

// Load will load plugin state from a byte array.
func (p *ReminderPlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}
	go p.Run(bot, service)
	return nil
}

// Save will save plugin state to a byte array.
func (p *ReminderPlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

// New will create a new Reminder plugin.
func New() bruxism.Plugin {
	p := &ReminderPlugin{
		SimplePlugin: *bruxism.NewSimplePlugin("Reminder"),
		Reminders:    []*Reminder{},
	}
	p.MessageFunc = p.messageFunc
	p.HelpFunc = p.helpFunc
	return p
}
<commit_msg>Fix reminder<commit_after>package reminderplugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/dustin/go-humanize"
	"github.com/iopred/bruxism"
)

// A Reminder holds data about a specific reminder.
type Reminder struct {
	StartTime time.Time
	Time      time.Time
	Requester string
	Target    string
	Message   string
	IsPrivate bool
}

// ReminderPlugin is a plugin that reminds users.
type ReminderPlugin struct {
	sync.RWMutex
	bruxism.SimplePlugin
	bot       *bruxism.Bot
	Reminders []*Reminder
}

var randomTimes = []string{
	"1 minute",
	"10 minutes",
	"1 hour",
	"4 hours",
	"tomorrow",
	"next week",
}

var randomMessages = []string{
	"walk the dog",
	"take pizza out of the oven",
	"check my email",
	"feed the baby",
	"play some quake",
}

func (p *ReminderPlugin) random(list []string) string {
	return list[rand.Intn(len(list))]
}

func (p *ReminderPlugin) randomReminder(service bruxism.Service) string {
	ticks := ""
	if service.Name() == bruxism.DiscordServiceName {
		ticks = "`"
	}

	return fmt.Sprintf("%s%sreminder %s %s%s", ticks, service.CommandPrefix(), p.random(randomTimes), p.random(randomMessages), ticks)
}

func (p *ReminderPlugin) helpFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message, detailed bool) []string {
	help := []string{
		bruxism.CommandHelp(service, "reminder", "<time> <reminder>", "Sets a reminder that is sent after the provided time.")[0],
	}
	if detailed {
		help = append(help, []string{
			"Examples: ",
			p.randomReminder(service),
			p.randomReminder(service),
		}...)
	}
	return help
}

func (p *ReminderPlugin) parseReminder(parts []string) (time.Time, string, error) {
	if parts[0] == "tomorrow" {
		return time.Now().Add(1 * time.Hour * 24), strings.Join(parts[1:], " "), nil
	}

	if parts[0] == "next" {
		switch parts[1] {
		case "week":
			return time.Now().Add(1 * time.Hour * 24 * 7), strings.Join(parts[2:], " "), nil
		case "month":
			return time.Now().Add(1 * time.Hour * 24 * 7 * 4), strings.Join(parts[2:], " "), nil
		case "year":
			return time.Now().Add(1 * time.Hour * 24 * 365), strings.Join(parts[2:], " "), nil
		default:
			return time.Time{}, "", errors.New("Invalid next.")
		}
	}

	i, err := strconv.Atoi(parts[0])
	if err != nil {
		return time.Time{}, "", err
	}

	switch {
	case strings.HasPrefix(parts[1], "sec"):
		return time.Now().Add(time.Duration(i) * time.Second), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "min"):
		return time.Now().Add(time.Duration(i) * time.Minute), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "hour"):
		return time.Now().Add(time.Duration(i) * time.Hour), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "day"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "week"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24 * 7), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "month"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24 * 7 * 4), strings.Join(parts[2:], " "), nil
	case strings.HasPrefix(parts[1], "year"):
		return time.Now().Add(time.Duration(i) * time.Hour * 24 * 365), strings.Join(parts[2:], " "), nil
	}

	return time.Time{}, "", errors.New("Invalid string.")
}

// AddReminder adds a reminder.
func (p *ReminderPlugin) AddReminder(reminder *Reminder) error {
	p.Lock()
	defer p.Unlock()

	i := 0
	for _, r := range p.Reminders {
		if r.Requester == reminder.Requester {
			i++
			if i > 5 {
				return errors.New("You have too many reminders already.")
			}
		}
	}

	i = 0
	for _, r := range p.Reminders {
		if r.Time.After(reminder.Time) {
			break
		}
		i++
	}

	p.Reminders = append(p.Reminders, reminder)
	copy(p.Reminders[i+1:], p.Reminders[i:])
	p.Reminders[i] = reminder

	return nil
}

func (p *ReminderPlugin) messageFunc(bot *bruxism.Bot, service bruxism.Service, message bruxism.Message) {
	if !service.IsMe(message) {
		if bruxism.MatchesCommand(service, "remind", message) || bruxism.MatchesCommand(service, "reminder", message) {
			_, parts := bruxism.ParseCommand(service, message)

			if len(parts) < 2 {
				service.SendMessage(message.Channel(), fmt.Sprintf("Invalid reminder, no time or message. eg: %s", p.randomReminder(service)))
				return
			}

			t, r, err := p.parseReminder(parts)

			now := time.Now()

			if err != nil || t.Before(now) || t.After(now.Add(time.Hour*24*365+time.Hour)) {
				service.SendMessage(message.Channel(), fmt.Sprintf("Invalid time. eg: %s", strings.Join(randomTimes, ", ")))
				return
			}

			if r == "" {
				service.SendMessage(message.Channel(), fmt.Sprintf("Invalid reminder, no message. eg: %s", p.randomReminder(service)))
				return
			}

			requester := message.UserName()
			if service.Name() == bruxism.DiscordServiceName {
				requester = fmt.Sprintf("<@%s>", message.UserID())
			}

			err = p.AddReminder(&Reminder{
				StartTime: now,
				Time:      t,
				Requester: requester,
				Target:    message.Channel(),
				Message:   r,
				IsPrivate: service.IsPrivate(message),
			})
			if err != nil {
				service.SendMessage(message.Channel(), err.Error())
				return
			}

			service.SendMessage(message.Channel(), fmt.Sprintf("Reminder set for %s.", humanize.Time(t)))
		}
	}
}

// SendReminder sends a reminder.
func (p *ReminderPlugin) SendReminder(service bruxism.Service, reminder *Reminder) {
	if reminder.IsPrivate {
		service.SendMessage(reminder.Target, fmt.Sprintf("%s you set a reminder: %s", humanize.Time(reminder.StartTime), reminder.Message))
	} else {
		service.SendMessage(reminder.Target, fmt.Sprintf("%s %s set a reminder: %s", humanize.Time(reminder.StartTime), reminder.Requester, reminder.Message))
	}
}

// Run will block until a reminder needs to be fired and then fire it.
func (p *ReminderPlugin) Run(bot *bruxism.Bot, service bruxism.Service) {
	for {
		p.RLock()

		if len(p.Reminders) > 0 {
			reminder := p.Reminders[0]
			if time.Now().After(reminder.Time) {
				p.SendReminder(service, reminder)
				p.Reminders = p.Reminders[1:]
				p.RUnlock()
				continue
			}
		}

		p.RUnlock()
		time.Sleep(500 * time.Millisecond)
	}
}

// Load will load plugin state from a byte array.
func (p *ReminderPlugin) Load(bot *bruxism.Bot, service bruxism.Service, data []byte) error {
	if data != nil {
		if err := json.Unmarshal(data, p); err != nil {
			log.Println("Error loading data", err)
		}
	}
	go p.Run(bot, service)
	return nil
}

// Save will save plugin state to a byte array.
func (p *ReminderPlugin) Save() ([]byte, error) {
	return json.Marshal(p)
}

// New will create a new Reminder plugin.
func New() bruxism.Plugin {
	p := &ReminderPlugin{
		SimplePlugin: *bruxism.NewSimplePlugin("Reminder"),
		Reminders:    []*Reminder{},
	}
	p.MessageFunc = p.messageFunc
	p.HelpFunc = p.helpFunc
	return p
}
<|endoftext|>


<commit_before>package acl

import (
	"testing"
)

func TestRootACL(t *testing.T) {
	if RootACL("allow") != AllowAll() {
		t.Fatalf("Bad root")
	}
	if RootACL("deny") != DenyAll() {
		t.Fatalf("Bad root")
	}
	if RootACL("manage") != ManageAll() {
		t.Fatalf("Bad root")
	}
	if RootACL("foo") != nil {
		t.Fatalf("bad root")
	}
}

func TestStaticACL(t *testing.T) {
	all := AllowAll()
	if _, ok := all.(*StaticACL); !ok {
		t.Fatalf("expected static")
	}

	none := DenyAll()
	if _, ok := none.(*StaticACL); !ok {
		t.Fatalf("expected static")
	}

	manage := ManageAll()
	if _, ok := none.(*StaticACL); !ok {
		t.Fatalf("expected static")
	}

	if !all.KeyRead("foobar") {
		t.Fatalf("should allow")
	}
	if !all.KeyWrite("foobar") {
		t.Fatalf("should allow")
	}
	if !all.ServiceRead("foobar") {
		t.Fatalf("should allow")
	}
	if !all.ServiceWrite("foobar") {
		t.Fatalf("should allow")
	}
	if all.ACLList() {
		t.Fatalf("should not allow")
	}
	if all.ACLModify() {
		t.Fatalf("should not allow")
	}

	if none.KeyRead("foobar") {
		t.Fatalf("should not allow")
	}
	if none.KeyWrite("foobar") {
		t.Fatalf("should not allow")
	}
	if none.ServiceRead("foobar") {
		t.Fatalf("should not allow")
	}
	if none.ServiceWrite("foobar") {
		t.Fatalf("should not allow")
	}
	if none.EventRead("foobar") {
		t.Fatalf("should not allow")
	}
	if none.EventRead("") {
		t.Fatalf("should not allow")
	}
	if none.EventWrite("foobar") {
		t.Fatalf("should not allow")
	}
	if none.EventWrite("") {
		t.Fatalf("should not allow")
	}
	if none.ACLList() {
		t.Fatalf("should not noneow")
	}
	if none.ACLModify() {
		t.Fatalf("should not noneow")
	}

	if !manage.KeyRead("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.KeyWrite("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.ServiceRead("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.ServiceWrite("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.ACLList() {
		t.Fatalf("should allow")
	}
	if !manage.ACLModify() {
		t.Fatalf("should allow")
	}
}

func TestPolicyACL(t *testing.T) {
	all := AllowAll()
	policy := &Policy{
		Keys: []*KeyPolicy{
			&KeyPolicy{
				Prefix: "foo/",
				Policy: KeyPolicyWrite,
			},
			&KeyPolicy{
				Prefix: "foo/priv/",
				Policy: KeyPolicyDeny,
			},
			&KeyPolicy{
				Prefix: "bar/",
				Policy: KeyPolicyDeny,
			},
			&KeyPolicy{
				Prefix: "zip/",
				Policy: KeyPolicyRead,
			},
		},
		Services: []*ServicePolicy{
			&ServicePolicy{
				Name:   "",
				Policy: ServicePolicyWrite,
			},
			&ServicePolicy{
				Name:   "foo",
				Policy: ServicePolicyRead,
			},
			&ServicePolicy{
				Name:   "bar",
				Policy: ServicePolicyDeny,
			},
			&ServicePolicy{
				Name:   "barfoo",
				Policy: ServicePolicyWrite,
			},
		},
		Events: []*EventPolicy{
			&EventPolicy{
				Event:  "",
				Policy: EventPolicyRead,
			},
			&EventPolicy{
				Event:  "foo",
				Policy: EventPolicyWrite,
			},
			&EventPolicy{
				Event:  "bar",
				Policy: EventPolicyDeny,
			},
		},
	}
	acl, err := New(all, policy)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	type keycase struct {
		inp         string
		read        bool
		write       bool
		writePrefix bool
	}
	cases := []keycase{
		{"other", true, true, true},
		{"foo/test", true, true, true},
		{"foo/priv/test", false, false, false},
		{"bar/any", false, false, false},
		{"zip/test", true, false, false},
		{"foo/", true, true, false},
		{"", true, true, false},
	}
	for _, c := range cases {
		if c.read != acl.KeyRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.KeyWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
		if c.writePrefix != acl.KeyWritePrefix(c.inp) {
			t.Fatalf("Write prefix fail: %#v", c)
		}
	}

	// Test the services
	type servicecase struct {
		inp   string
		read  bool
		write bool
	}
	scases := []servicecase{
		{"other", true, true},
		{"foo", true, false},
		{"bar", false, false},
		{"foobar", true, false},
		{"barfo", false, false},
		{"barfoo", true, true},
		{"barfoo2", true, true},
	}
	for _, c := range scases {
		if c.read != acl.ServiceRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.ServiceWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
	}

	type eventcase struct {
		inp   string
		read  bool
		write bool
	}
	eventcases := []eventcase{
		{"foo", true, true},
		{"foobar", true, true},
		{"bar", false, false},
		{"barbaz", false, false},
		{"baz", true, false},
	}
	for _, c := range eventcases {
		if c.read != acl.EventRead(c.inp) {
			t.Fatalf("Event fail: %#v", c)
		}
		if c.write != acl.EventWrite(c.inp) {
			t.Fatalf("Event fail: %#v", c)
		}
	}
}

func TestPolicyACL_Parent(t *testing.T) {
	deny := DenyAll()
	policyRoot := &Policy{
		Keys: []*KeyPolicy{
			&KeyPolicy{
				Prefix: "foo/",
				Policy: KeyPolicyWrite,
			},
			&KeyPolicy{
				Prefix: "bar/",
				Policy: KeyPolicyRead,
			},
		},
		Services: []*ServicePolicy{
			&ServicePolicy{
				Name:   "other",
				Policy: ServicePolicyWrite,
			},
			&ServicePolicy{
				Name:   "foo",
				Policy: ServicePolicyRead,
			},
		},
	}
	root, err := New(deny, policyRoot)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	policy := &Policy{
		Keys: []*KeyPolicy{
			&KeyPolicy{
				Prefix: "foo/priv/",
				Policy: KeyPolicyRead,
			},
			&KeyPolicy{
				Prefix: "bar/",
				Policy: KeyPolicyDeny,
			},
			&KeyPolicy{
				Prefix: "zip/",
				Policy: KeyPolicyRead,
			},
		},
		Services: []*ServicePolicy{
			&ServicePolicy{
				Name:   "bar",
				Policy: ServicePolicyDeny,
			},
		},
	}
	acl, err := New(root, policy)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	type keycase struct {
		inp         string
		read        bool
		write       bool
		writePrefix bool
	}
	cases := []keycase{
		{"other", false, false, false},
		{"foo/test", true, true, true},
		{"foo/priv/test", true, false, false},
		{"bar/any", false, false, false},
		{"zip/test", true, false, false},
	}
	for _, c := range cases {
		if c.read != acl.KeyRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.KeyWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
		if c.writePrefix != acl.KeyWritePrefix(c.inp) {
			t.Fatalf("Write prefix fail: %#v", c)
		}
	}

	// Test the services
	type servicecase struct {
		inp   string
		read  bool
		write bool
	}
	scases := []servicecase{
		{"fail", false, false},
		{"other", true, true},
		{"foo", true, false},
		{"bar", false, false},
	}
	for _, c := range scases {
		if c.read != acl.ServiceRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.ServiceWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
	}
}
<commit_msg>acl: fix spelling in tests<commit_after>package acl

import (
	"testing"
)

func TestRootACL(t *testing.T) {
	if RootACL("allow") != AllowAll() {
		t.Fatalf("Bad root")
	}
	if RootACL("deny") != DenyAll() {
		t.Fatalf("Bad root")
	}
	if RootACL("manage") != ManageAll() {
		t.Fatalf("Bad root")
	}
	if RootACL("foo") != nil {
		t.Fatalf("bad root")
	}
}

func TestStaticACL(t *testing.T) {
	all := AllowAll()
	if _, ok := all.(*StaticACL); !ok {
		t.Fatalf("expected static")
	}

	none := DenyAll()
	if _, ok := none.(*StaticACL); !ok {
		t.Fatalf("expected static")
	}

	manage := ManageAll()
	if _, ok := none.(*StaticACL); !ok {
		t.Fatalf("expected static")
	}

	if !all.KeyRead("foobar") {
		t.Fatalf("should allow")
	}
	if !all.KeyWrite("foobar") {
		t.Fatalf("should allow")
	}
	if !all.ServiceRead("foobar") {
		t.Fatalf("should allow")
	}
	if !all.ServiceWrite("foobar") {
		t.Fatalf("should allow")
	}
	if all.ACLList() {
		t.Fatalf("should not allow")
	}
	if all.ACLModify() {
		t.Fatalf("should not allow")
	}

	if none.KeyRead("foobar") {
		t.Fatalf("should not allow")
	}
	if none.KeyWrite("foobar") {
		t.Fatalf("should not allow")
	}
	if none.ServiceRead("foobar") {
		t.Fatalf("should not allow")
	}
	if none.ServiceWrite("foobar") {
		t.Fatalf("should not allow")
	}
	if none.EventRead("foobar") {
		t.Fatalf("should not allow")
	}
	if none.EventRead("") {
		t.Fatalf("should not allow")
	}
	if none.EventWrite("foobar") {
		t.Fatalf("should not allow")
	}
	if none.EventWrite("") {
		t.Fatalf("should not allow")
	}
	if none.ACLList() {
		t.Fatalf("should not allow")
	}
	if none.ACLModify() {
		t.Fatalf("should not allow")
	}

	if !manage.KeyRead("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.KeyWrite("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.ServiceRead("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.ServiceWrite("foobar") {
		t.Fatalf("should allow")
	}
	if !manage.ACLList() {
		t.Fatalf("should allow")
	}
	if !manage.ACLModify() {
		t.Fatalf("should allow")
	}
}

func TestPolicyACL(t *testing.T) {
	all := AllowAll()
	policy := &Policy{
		Keys: []*KeyPolicy{
			&KeyPolicy{
				Prefix: "foo/",
				Policy: KeyPolicyWrite,
			},
			&KeyPolicy{
				Prefix: "foo/priv/",
				Policy: KeyPolicyDeny,
			},
			&KeyPolicy{
				Prefix: "bar/",
				Policy: KeyPolicyDeny,
			},
			&KeyPolicy{
				Prefix: "zip/",
				Policy: KeyPolicyRead,
			},
		},
		Services: []*ServicePolicy{
			&ServicePolicy{
				Name:   "",
				Policy: ServicePolicyWrite,
			},
			&ServicePolicy{
				Name:   "foo",
				Policy: ServicePolicyRead,
			},
			&ServicePolicy{
				Name:   "bar",
				Policy: ServicePolicyDeny,
			},
			&ServicePolicy{
				Name:   "barfoo",
				Policy: ServicePolicyWrite,
			},
		},
		Events: []*EventPolicy{
			&EventPolicy{
				Event:  "",
				Policy: EventPolicyRead,
			},
			&EventPolicy{
				Event:  "foo",
				Policy: EventPolicyWrite,
			},
			&EventPolicy{
				Event:  "bar",
				Policy: EventPolicyDeny,
			},
		},
	}
	acl, err := New(all, policy)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	type keycase struct {
		inp         string
		read        bool
		write       bool
		writePrefix bool
	}
	cases := []keycase{
		{"other", true, true, true},
		{"foo/test", true, true, true},
		{"foo/priv/test", false, false, false},
		{"bar/any", false, false, false},
		{"zip/test", true, false, false},
		{"foo/", true, true, false},
		{"", true, true, false},
	}
	for _, c := range cases {
		if c.read != acl.KeyRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.KeyWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
		if c.writePrefix != acl.KeyWritePrefix(c.inp) {
			t.Fatalf("Write prefix fail: %#v", c)
		}
	}

	// Test the services
	type servicecase struct {
		inp   string
		read  bool
		write bool
	}
	scases := []servicecase{
		{"other", true, true},
		{"foo", true, false},
		{"bar", false, false},
		{"foobar", true, false},
		{"barfo", false, false},
		{"barfoo", true, true},
		{"barfoo2", true, true},
	}
	for _, c := range scases {
		if c.read != acl.ServiceRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.ServiceWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
	}

	type eventcase struct {
		inp   string
		read  bool
		write bool
	}
	eventcases := []eventcase{
		{"foo", true, true},
		{"foobar", true, true},
		{"bar", false, false},
		{"barbaz", false, false},
		{"baz", true, false},
	}
	for _, c := range eventcases {
		if c.read != acl.EventRead(c.inp) {
			t.Fatalf("Event fail: %#v", c)
		}
		if c.write != acl.EventWrite(c.inp) {
			t.Fatalf("Event fail: %#v", c)
		}
	}
}

func TestPolicyACL_Parent(t *testing.T) {
	deny := DenyAll()
	policyRoot := &Policy{
		Keys: []*KeyPolicy{
			&KeyPolicy{
				Prefix: "foo/",
				Policy: KeyPolicyWrite,
			},
			&KeyPolicy{
				Prefix: "bar/",
				Policy: KeyPolicyRead,
			},
		},
		Services: []*ServicePolicy{
			&ServicePolicy{
				Name:   "other",
				Policy: ServicePolicyWrite,
			},
			&ServicePolicy{
				Name:   "foo",
				Policy: ServicePolicyRead,
			},
		},
	}
	root, err := New(deny, policyRoot)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	policy := &Policy{
		Keys: []*KeyPolicy{
			&KeyPolicy{
				Prefix: "foo/priv/",
				Policy: KeyPolicyRead,
			},
			&KeyPolicy{
				Prefix: "bar/",
				Policy: KeyPolicyDeny,
			},
			&KeyPolicy{
				Prefix: "zip/",
				Policy: KeyPolicyRead,
			},
		},
		Services: []*ServicePolicy{
			&ServicePolicy{
				Name:   "bar",
				Policy: ServicePolicyDeny,
			},
		},
	}
	acl, err := New(root, policy)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	type keycase struct {
		inp         string
		read        bool
		write       bool
		writePrefix bool
	}
	cases := []keycase{
		{"other", false, false, false},
		{"foo/test", true, true, true},
		{"foo/priv/test", true, false, false},
		{"bar/any", false, false, false},
		{"zip/test", true, false, false},
	}
	for _, c := range cases {
		if c.read != acl.KeyRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.KeyWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
		if c.writePrefix != acl.KeyWritePrefix(c.inp) {
			t.Fatalf("Write prefix fail: %#v", c)
		}
	}

	// Test the services
	type servicecase struct {
		inp   string
		read  bool
		write bool
	}
	scases := []servicecase{
		{"fail", false, false},
		{"other", true, true},
		{"foo", true, false},
		{"bar", false, false},
	}
	for _, c := range scases {
		if c.read != acl.ServiceRead(c.inp) {
			t.Fatalf("Read fail: %#v", c)
		}
		if c.write != acl.ServiceWrite(c.inp) {
			t.Fatalf("Write fail: %#v", c)
		}
	}
}
<|endoftext|>


<commit_before>package etcd

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

	"golang.org/x/net/context"

	e "github.com/coreos/etcd/client"
	"github.com/portworx/kvdb"
)

const (
	Name    = "etcd-kv"
	defHost = "http://etcd.portworx.com:4001"
)

type EtcdKV struct {
	client e.KeysAPI
	domain string
}

type etcdLock struct {
	done     chan struct{}
	unlocked bool
	err      error
	sync.Mutex
}

func EtcdInit(domain string,
	machines []string,
	options map[string]string,
) (kvdb.Kvdb, error) {

	if len(machines) == 0 {
		machines = []string{defHost}
	}
	cfg := e.Config{
		Endpoints: machines,
		Transport: e.DefaultTransport,
		// The time required for a request to fail - 10 sec
		HeaderTimeoutPerRequest: time.Duration(10) * time.Second,
	}
	c, err := e.New(cfg)
	if err != nil {
		return nil, err
	}
	if domain != "" && !strings.HasSuffix(domain, "/") {
		domain = domain + "/"
	}
	kvapi := e.NewKeysAPI(c)
	kv := &EtcdKV{
		client: kvapi,
		domain: domain,
	}
	return kv, nil
}

func (kv *EtcdKV) String() string {
	return Name
}

func (kv *EtcdKV) nodeToKv(node *e.Node) *kvdb.KVPair {

	kvp := &kvdb.KVPair{
		Value:         []byte(node.Value),
		TTL:           node.TTL,
		ModifiedIndex: node.ModifiedIndex,
		CreatedIndex:  node.CreatedIndex,
	}

	// Strip out the leading '/'
	if len(node.Key) != 0 {
		kvp.Key = node.Key[1:]
	} else {
		kvp.Key = node.Key
	}
	kvp.Key = strings.TrimPrefix(kvp.Key, kv.domain)
	return kvp
}

func (kv *EtcdKV) resultToKv(result *e.Response) *kvdb.KVPair {

	kvp := kv.nodeToKv(result.Node)
	switch result.Action {
	case "create":
		kvp.Action = kvdb.KVCreate
	case "set", "update":
		kvp.Action = kvdb.KVSet
	case "delete":
		kvp.Action = kvdb.KVDelete
	case "get":
		kvp.Action = kvdb.KVGet
	default:
		kvp.Action = kvdb.KVUknown
	}
	kvp.KVDBIndex = result.Index
	return kvp
}

func (kv *EtcdKV) resultToKvs(result *e.Response) kvdb.KVPairs {

	kvs := make([]*kvdb.KVPair, len(result.Node.Nodes))
	for i := range result.Node.Nodes {
		kvs[i] = kv.nodeToKv(result.Node.Nodes[i])
		kvs[i].KVDBIndex = result.Index
	}
	return kvs
}

func (kv *EtcdKV) Get(key string) (*kvdb.KVPair, error) {
	key = kv.domain + key
	result, err := kv.client.Get(context.Background(), key, &e.GetOptions{
		Recursive: false,
		Sort:      false,
	})
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) GetVal(key string, val interface{}) (*kvdb.KVPair, error) {
	kvp, err := kv.Get(key)
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(kvp.Value, val)
	return kvp, err
}

func (kv *EtcdKV) toBytes(val interface{}) ([]byte, error) {
	var (
		b   []byte
		err error
	)

	switch val.(type) {
	case string:
		b = []byte(val.(string))
	case []byte:
		b = val.([]byte)
	default:
		b, err = json.Marshal(val)
		if err != nil {
			return nil, err
		}
	}

	return b, nil
}

func (kv *EtcdKV) Put(
	key string,
	val interface{},
	ttl uint64,
) (*kvdb.KVPair, error) {

	key = kv.domain + key
	b, err := kv.toBytes(val)
	if err != nil {
		return nil, err
	}
	result, err := kv.client.Set(
		context.Background(),
		key,
		string(b),
		&e.SetOptions{
			TTL: time.Duration(ttl) * time.Second,
		},
	)
	if err != nil {
		return nil, err
	}

	return kv.resultToKv(result), err
}

func (kv *EtcdKV) Create(
	key string,
	val interface{},
	ttl uint64,
) (*kvdb.KVPair, error) {

	key = kv.domain + key

	b, err := kv.toBytes(val)
	if err != nil {
		return nil, err
	}
	result, err := kv.client.Set(context.Background(), key, string(b), &e.SetOptions{
		TTL:       time.Duration(ttl) * time.Second,
		PrevExist: e.PrevNoExist,
	})
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) Update(
	key string,
	val interface{},
	ttl uint64,
) (*kvdb.KVPair, error) {

	key = kv.domain + key

	b, err := kv.toBytes(val)
	if err != nil {
		return nil, err
	}
	result, err := kv.client.Set(context.Background(), key, string(b), &e.SetOptions{
		TTL:       time.Duration(ttl) * time.Second,
		PrevExist: e.PrevExist,
	})
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) Enumerate(prefix string) (kvdb.KVPairs, error) {
	prefix = kv.domain + prefix

	result, err := kv.client.Get(context.Background(), prefix, &e.GetOptions{
		Recursive: true,
		Sort:      true,
	})
	if err != nil {
		return nil, err
	}
	return kv.resultToKvs(result), err
}

func (kv *EtcdKV) Delete(key string) (*kvdb.KVPair, error) {
	key = kv.domain + key

	result, err := kv.client.Delete(context.Background(), key, &e.DeleteOptions{
		Recursive: false,
	})
	if err == nil {
		return kv.resultToKv(result), err
	}
	return nil, err
}

func (kv *EtcdKV) DeleteTree(prefix string) error {
	prefix = kv.domain + prefix

	_, err := kv.client.Delete(context.Background(), prefix, &e.DeleteOptions{
		Recursive: true,
	})
	return err
}

func (kv *EtcdKV) Keys(prefix, key string) ([]string, error) {
	return nil, kvdb.ErrNotSupported
}

func (kv *EtcdKV) CompareAndSet(
	kvp *kvdb.KVPair,
	flags kvdb.KVFlags,
	prevValue []byte,
) (*kvdb.KVPair, error) {

	opts := &e.SetOptions{
		PrevValue: string(prevValue),
	}
	if (flags & kvdb.KVModifiedIndex) != 0 {
		opts.PrevIndex = kvp.ModifiedIndex
	}
	if (flags & kvdb.KVTTL) != 0 {
		opts.TTL = time.Duration(kvp.TTL)
	}
	result, err := kv.client.Set(
		context.Background(),
		kv.domain+kvp.Key,
		string(kvp.Value),
		opts,
	)
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) CompareAndDelete(
	kvp *kvdb.KVPair,
	flags kvdb.KVFlags,
) (*kvdb.KVPair, error) {

	result, err := kv.client.Delete(
		context.Background(),
		kv.domain+kvp.Key,
		&e.DeleteOptions{
			PrevValue: string(kvp.Value),
			PrevIndex: kvp.ModifiedIndex,
		})
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) WatchKey(
	key string,
	waitIndex uint64,
	opaque interface{},
	cb kvdb.WatchCB,
) error {

	key = kv.domain + key
	go kv.watchStart(key, false, waitIndex, opaque, cb)
	return nil
}

func (kv *EtcdKV) WatchTree(
	prefix string,
	waitIndex uint64,
	opaque interface{},
	cb kvdb.WatchCB,
) error {

	prefix = kv.domain + prefix
	go kv.watchStart(prefix, true, waitIndex, opaque, cb)
	return nil
}

func (kv *EtcdKV) Lock(key string, ttl uint64) (*kvdb.KVPair, error) {
	key = kv.domain + key
	duration := time.Second

	count := 0
	kvPair, err := kv.Create(key, "locked", ttl)
	for maxCount := 300; err != nil && count < maxCount; count++ {
		time.Sleep(duration)
		kvPair, err = kv.Create(key, "locked", ttl)
	}
	if err != nil {
		return nil, err
	}
	if count > 10 {
		fmt.Printf("ETCD: spent %v iterations locking %v\n", count, key)
	}
	kvPair.TTL = int64(time.Duration(ttl) * time.Second)
	kvPair.Lock = &etcdLock{done: make(chan struct{})}
	go kv.refreshLock(kvPair)

	return kvPair, err
}

func (kv *EtcdKV) refreshLock(kvPair *kvdb.KVPair) {

	l := kvPair.Lock.(*etcdLock)
	ttl := kvPair.TTL
	refresh := time.NewTicker(time.Duration(kvPair.TTL) / 4)
	defer refresh.Stop()
	for {
		select {
		case <-refresh.C:
			l.Lock()
			for !l.unlocked {
				kvPair.TTL = ttl
				kvp, err := kv.CompareAndSet(
					kvPair,
					kvdb.KVTTL|kvdb.KVModifiedIndex,
					kvPair.Value,
				)
				if err != nil {
					fmt.Printf(
						"Error refreshing lock for key %v: %v\n",
						string(kvp.Key), err,
					)
					l.err = err
					l.Unlock()
					return
				}
				kvPair.ModifiedIndex = kvp.ModifiedIndex
				break
			}
			l.Unlock()
		case <-l.done:
			return
		}
	}
}

func (kv *EtcdKV) Unlock(kvp *kvdb.KVPair) error {
	l, ok := kvp.Lock.(*etcdLock)
	if !ok {
		return fmt.Errorf("Invalid lock structure for key %v", string(kvp.Key))
	}
	l.Lock()
	// Don't modify kvp here, CompareAndDelete does that.
	_, err := kv.CompareAndDelete(kvp, kvdb.KVFlags(0))
	if err == nil {
		l.unlocked = true
		l.Unlock()
		l.done <- struct{}{}
		return nil
	}
	l.Unlock()
	return err
}

func (kv *EtcdKV) watchStart(key string,
	recursive bool,
	waitIndex uint64,
	opaque interface{},
	cb kvdb.WatchCB,
) {
	ctx, cancel := context.WithCancel(context.Background())
	watcher := kv.client.Watcher(key, &e.WatcherOptions{
		AfterIndex: waitIndex,
		Recursive:  recursive,
	})
	isCancelSent := false
	for {
		r, watchErr := watcher.Next(ctx)
		if watchErr != nil && !isCancelSent {
			e, ok := watchErr.(e.Error)
			if ok {
				fmt.Printf("Etcd error code %d Message %s Cause %s Index %ju\n",
					e.Code, e.Message, e.Cause, e.Index)
			} else {
				fmt.Printf("Etcd returned an error : %s", watchErr.Error())
			}
			cb(key, opaque, nil, watchErr)
		} else if watchErr == nil && !isCancelSent {
			err := cb(key, opaque, kv.resultToKv(r), nil)
			if err != nil {
				// Cancel the context
				cancel()
				isCancelSent = true
			}
		} else {
			cb(key, opaque, nil, kvdb.ErrWatchStopped)
			break
		}
	}
}

func (kv *EtcdKV) TxNew() (kvdb.Tx, error) {
	return nil, kvdb.ErrNotSupported
}

func init() {
	kvdb.Register(Name, EtcdInit)
}
<commit_msg>Add retry logic for set/get/create.<commit_after>package etcd

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

	"golang.org/x/net/context"

	e "github.com/coreos/etcd/client"
	"github.com/portworx/kvdb"
)

const (
	Name                          = "etcd-kv"
	defHost                       = "http://etcd.portworx.com:4001"
	defaultRetryCount             = 5
	defaultIntervalBetweenRetries = time.Millisecond * 500
)

type EtcdKV struct {
	client e.KeysAPI
	domain string
}

type etcdLock struct {
	done     chan struct{}
	unlocked bool
	err      error
	sync.Mutex
}

func EtcdInit(domain string,
	machines []string,
	options map[string]string,
) (kvdb.Kvdb, error) {

	if len(machines) == 0 {
		machines = []string{defHost}
	}
	cfg := e.Config{
		Endpoints: machines,
		Transport: e.DefaultTransport,
		// The time required for a request to fail - 10 sec
		HeaderTimeoutPerRequest: time.Duration(10) * time.Second,
	}
	c, err := e.New(cfg)
	if err != nil {
		return nil, err
	}
	if domain != "" && !strings.HasSuffix(domain, "/") {
		domain = domain + "/"
	}
	kvapi := e.NewKeysAPI(c)
	kv := &EtcdKV{
		client: kvapi,
		domain: domain,
	}
	return kv, nil
}

func (kv *EtcdKV) String() string {
	return Name
}

func (kv *EtcdKV) nodeToKv(node *e.Node) *kvdb.KVPair {

	kvp := &kvdb.KVPair{
		Value:         []byte(node.Value),
		TTL:           node.TTL,
		ModifiedIndex: node.ModifiedIndex,
		CreatedIndex:  node.CreatedIndex,
	}

	// Strip out the leading '/'
	if len(node.Key) != 0 {
		kvp.Key = node.Key[1:]
	} else {
		kvp.Key = node.Key
	}
	kvp.Key = strings.TrimPrefix(kvp.Key, kv.domain)
	return kvp
}

func (kv *EtcdKV) resultToKv(result *e.Response) *kvdb.KVPair {

	kvp := kv.nodeToKv(result.Node)
	switch result.Action {
	case "create":
		kvp.Action = kvdb.KVCreate
	case "set", "update":
		kvp.Action = kvdb.KVSet
	case "delete":
		kvp.Action = kvdb.KVDelete
	case "get":
		kvp.Action = kvdb.KVGet
	default:
		kvp.Action = kvdb.KVUknown
	}
	kvp.KVDBIndex = result.Index
	return kvp
}

func (kv *EtcdKV) resultToKvs(result *e.Response) kvdb.KVPairs {

	kvs := make([]*kvdb.KVPair, len(result.Node.Nodes))
	for i := range result.Node.Nodes {
		kvs[i] = kv.nodeToKv(result.Node.Nodes[i])
		kvs[i].KVDBIndex = result.Index
	}
	return kvs
}

func (kv *EtcdKV) get(key string, recursive, sort bool) (*kvdb.KVPair, error) {
	var err error
	for i := 0; i < defaultRetryCount; i++ {
		result, err := kv.client.Get(context.Background(), key, &e.GetOptions{
			Recursive: recursive,
			Sort:      sort,
		})
		switch {
		case err == nil:
			return kv.resultToKv(result), err
		case err == e.ErrClusterUnavailable:
			time.Sleep(defaultIntervalBetweenRetries)
		default:
			return nil, err
		}
	}
	return nil, err
}

func (kv *EtcdKV) Get(key string) (*kvdb.KVPair, error) {
	key = kv.domain + key
	return kv.get(key, false, false)
}

func (kv *EtcdKV) GetVal(key string, val interface{}) (*kvdb.KVPair, error) {
	kvp, err := kv.Get(key)
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(kvp.Value, val)
	return kvp, err
}

func (kv *EtcdKV) toBytes(val interface{}) ([]byte, error) {
	var (
		b   []byte
		err error
	)

	switch val.(type) {
	case string:
		b = []byte(val.(string))
	case []byte:
		b = val.([]byte)
	default:
		b, err = json.Marshal(val)
		if err != nil {
			return nil, err
		}
	}

	return b, nil
}

func (kv *EtcdKV) setWithRetry(ctx context.Context, key, value string,
	opts *e.SetOptions) (*kvdb.KVPair, error) {
	var err error
	for i := 0; i < defaultRetryCount; i++ {
		result, err := kv.client.Set(ctx, key, value, opts)
		switch {
		case err == nil:
			return kv.resultToKv(result), err
		case err == e.ErrClusterUnavailable:
			time.Sleep(defaultIntervalBetweenRetries)
		default:
			return nil, err
		}
	}
	return nil, err
}

func (kv *EtcdKV) Put(
	key string,
	val interface{},
	ttl uint64,
) (*kvdb.KVPair, error) {

	key = kv.domain + key
	b, err := kv.toBytes(val)
	if err != nil {
		return nil, err
	}
	return kv.setWithRetry(
		context.Background(),
		key,
		string(b),
		&e.SetOptions{
			TTL: time.Duration(ttl) * time.Second,
		})
}

func (kv *EtcdKV) Create(
	key string,
	val interface{},
	ttl uint64,
) (*kvdb.KVPair, error) {

	key = kv.domain + key

	b, err := kv.toBytes(val)
	if err != nil {
		return nil, err
	}
	return kv.setWithRetry(context.Background(), key, string(b), &e.SetOptions{
		TTL:       time.Duration(ttl) * time.Second,
		PrevExist: e.PrevNoExist,
	})
}

func (kv *EtcdKV) Update(
	key string,
	val interface{},
	ttl uint64,
) (*kvdb.KVPair, error) {

	key = kv.domain + key

	b, err := kv.toBytes(val)
	if err != nil {
		return nil, err
	}
	return kv.setWithRetry(context.Background(), key, string(b), &e.SetOptions{
		TTL:       time.Duration(ttl) * time.Second,
		PrevExist: e.PrevExist,
	})
}

func (kv *EtcdKV) Enumerate(prefix string) (kvdb.KVPairs, error) {
	prefix = kv.domain + prefix
	var err error
	for i := 0; i < defaultRetryCount; i++ {
		result, err := kv.client.Get(context.Background(), prefix, &e.GetOptions{
			Recursive: true,
			Sort:      true,
		})
		switch {
		case err == nil:
			return kv.resultToKvs(result), err
		case err == e.ErrClusterUnavailable:
			time.Sleep(defaultIntervalBetweenRetries)
		default:
			return nil, err
		}
	}
	return nil, err
}

func (kv *EtcdKV) Delete(key string) (*kvdb.KVPair, error) {
	key = kv.domain + key

	result, err := kv.client.Delete(context.Background(), key, &e.DeleteOptions{
		Recursive: false,
	})
	if err == nil {
		return kv.resultToKv(result), err
	}
	return nil, err
}

func (kv *EtcdKV) DeleteTree(prefix string) error {
	prefix = kv.domain + prefix

	_, err := kv.client.Delete(context.Background(), prefix, &e.DeleteOptions{
		Recursive: true,
	})
	return err
}

func (kv *EtcdKV) Keys(prefix, key string) ([]string, error) {
	return nil, kvdb.ErrNotSupported
}

func (kv *EtcdKV) CompareAndSet(
	kvp *kvdb.KVPair,
	flags kvdb.KVFlags,
	prevValue []byte,
) (*kvdb.KVPair, error) {

	opts := &e.SetOptions{
		PrevValue: string(prevValue),
	}
	if (flags & kvdb.KVModifiedIndex) != 0 {
		opts.PrevIndex = kvp.ModifiedIndex
	}
	if (flags & kvdb.KVTTL) != 0 {
		opts.TTL = time.Duration(kvp.TTL)
	}
	result, err := kv.client.Set(
		context.Background(),
		kv.domain+kvp.Key,
		string(kvp.Value),
		opts,
	)
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) CompareAndDelete(
	kvp *kvdb.KVPair,
	flags kvdb.KVFlags,
) (*kvdb.KVPair, error) {

	result, err := kv.client.Delete(
		context.Background(),
		kv.domain+kvp.Key,
		&e.DeleteOptions{
			PrevValue: string(kvp.Value),
			PrevIndex: kvp.ModifiedIndex,
		})
	if err != nil {
		return nil, err
	}
	return kv.resultToKv(result), err
}

func (kv *EtcdKV) WatchKey(
	key string,
	waitIndex uint64,
	opaque interface{},
	cb kvdb.WatchCB,
) error {

	key = kv.domain + key
	go kv.watchStart(key, false, waitIndex, opaque, cb)
	return nil
}

func (kv *EtcdKV) WatchTree(
	prefix string,
	waitIndex uint64,
	opaque interface{},
	cb kvdb.WatchCB,
) error {

	prefix = kv.domain + prefix
	go kv.watchStart(prefix, true, waitIndex, opaque, cb)
	return nil
}

func (kv *EtcdKV) Lock(key string, ttl uint64) (*kvdb.KVPair, error) {
	key = kv.domain + key
	duration := time.Second

	count := 0
	kvPair, err := kv.Create(key, "locked", ttl)
	for maxCount := 300; err != nil && count < maxCount; count++ {
		time.Sleep(duration)
		kvPair, err = kv.Create(key, "locked", ttl)
	}
	if err != nil {
		return nil, err
	}
	if count > 10 {
		fmt.Printf("ETCD: spent %v iterations locking %v\n", count, key)
	}
	kvPair.TTL = int64(time.Duration(ttl) * time.Second)
	kvPair.Lock = &etcdLock{done: make(chan struct{})}
	go kv.refreshLock(kvPair)

	return kvPair, err
}

func (kv *EtcdKV) refreshLock(kvPair *kvdb.KVPair) {

	l := kvPair.Lock.(*etcdLock)
	ttl := kvPair.TTL
	refresh := time.NewTicker(time.Duration(kvPair.TTL) / 4)
	defer refresh.Stop()
	for {
		select {
		case <-refresh.C:
			l.Lock()
			for !l.unlocked {
				kvPair.TTL = ttl
				kvp, err := kv.CompareAndSet(
					kvPair,
					kvdb.KVTTL|kvdb.KVModifiedIndex,
					kvPair.Value,
				)
				if err != nil {
					fmt.Printf(
						"Error refreshing lock for key %v: %v\n",
						string(kvp.Key), err,
					)
					l.err = err
					l.Unlock()
					return
				}
				kvPair.ModifiedIndex = kvp.ModifiedIndex
				break
			}
			l.Unlock()
		case <-l.done:
			return
		}
	}
}

func (kv *EtcdKV) Unlock(kvp *kvdb.KVPair) error {
	l, ok := kvp.Lock.(*etcdLock)
	if !ok {
		return fmt.Errorf("Invalid lock structure for key %v", string(kvp.Key))
	}
	l.Lock()
	// Don't modify kvp here, CompareAndDelete does that.
	_, err := kv.CompareAndDelete(kvp, kvdb.KVFlags(0))
	if err == nil {
		l.unlocked = true
		l.Unlock()
		l.done <- struct{}{}
		return nil
	}
	l.Unlock()
	return err
}

func (kv *EtcdKV) watchStart(key string,
	recursive bool,
	waitIndex uint64,
	opaque interface{},
	cb kvdb.WatchCB,
) {
	ctx, cancel := context.WithCancel(context.Background())
	watcher := kv.client.Watcher(key, &e.WatcherOptions{
		AfterIndex: waitIndex,
		Recursive:  recursive,
	})
	isCancelSent := false
	for {
		r, watchErr := watcher.Next(ctx)
		if watchErr != nil && !isCancelSent {
			e, ok := watchErr.(e.Error)
			if ok {
				fmt.Printf("Etcd error code %d Message %s Cause %s Index %ju\n",
					e.Code, e.Message, e.Cause, e.Index)
			} else {
				fmt.Printf("Etcd returned an error : %s", watchErr.Error())
			}
			cb(key, opaque, nil, watchErr)
		} else if watchErr == nil && !isCancelSent {
			err := cb(key, opaque, kv.resultToKv(r), nil)
			if err != nil {
				// Cancel the context
				cancel()
				isCancelSent = true
			}
		} else {
			cb(key, opaque, nil, kvdb.ErrWatchStopped)
			break
		}
	}
}

func (kv *EtcdKV) TxNew() (kvdb.Tx, error) {
	return nil, kvdb.ErrNotSupported
}

func init() {
	kvdb.Register(Name, EtcdInit)
}
<|endoftext|>