<commit_before>/*
Copyright 2018 The Doctl Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
	http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package commands

import (
	"fmt"
	"strings"

	"github.com/digitalocean/doctl"
	"github.com/digitalocean/doctl/commands/displayers"
	"github.com/digitalocean/doctl/do"
	"github.com/digitalocean/godo"
	"github.com/spf13/cobra"
)

// Projects creates the projects commands hierarchy.
func Projects() *Command {
	cmd := &Command{
		Command: &cobra.Command{
			Use:   "projects",
			Short: "projects commands",
			Long:  "projects commands are for creating and managing projects",
		},
	}

	CmdBuilder(cmd, RunProjectsList, "list", "list projects", Writer, aliasOpt("ls"), displayerType(&displayers.Project{}))
	CmdBuilder(cmd, RunProjectsGet, "get <id>", "get a project; use \"default\" as ID to get default project", Writer, aliasOpt("g"), displayerType(&displayers.Project{}))

	cmdProjectsCreate := CmdBuilder(cmd, RunProjectsCreate, "create", "create project", Writer, aliasOpt("c"), displayerType(&displayers.Project{}))
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectName, "", "", "project name", requiredOpt())
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectPurpose, "", "", "project purpose", requiredOpt())
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectDescription, "", "", "a description of your project")
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectEnvironment, "", "", "the environment in which your project resides. Should be one of 'Development', 'Staging', 'Production'.")

	cmdProjectsUpdate := CmdBuilder(cmd, RunProjectsUpdate, "update <id>", "update project; use \"default\" as ID to update the default project", Writer, aliasOpt("u"), displayerType(&displayers.Project{}))
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectName, "", "", "project name")
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectPurpose, "", "", "project purpose")
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectDescription, "", "", "a description of your project")
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectEnvironment, "", "", "the environment in which your project resides. Should be one of 'Development', 'Staging', 'Production'.")
	AddBoolFlag(cmdProjectsUpdate, doctl.ArgProjectIsDefault, "", false, "set the specified project as your default project")

	cmdProjectsDelete := CmdBuilder(cmd, RunProjectsDelete, "delete <id> [<id> ...]", "delete project", Writer, aliasOpt("d", "rm"))
	AddBoolFlag(cmdProjectsDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force project delete")

	cmd.AddCommand(ProjectResourcesCmd())

	return cmd
}

// ProjectResourcesCmd creates the project resources commands hierarchy.
func ProjectResourcesCmd() *Command {
	cmd := &Command{
		Command: &cobra.Command{
			Use:   "resources",
			Short: "project resources commands",
			Long:  "project resources commands are for assigning and listing resources in projects",
		},
	}
	CmdBuilder(cmd, RunProjectResourcesList, "list <project-id>", "list project resources", Writer, aliasOpt("ls"), displayerType(&displayers.ProjectResource{}))
	CmdBuilder(cmd, RunProjectResourcesGet, "get <urn>", "get a project resource by its URN", Writer, aliasOpt("g"))

	cmdProjectResourcesAssign := CmdBuilder(cmd, RunProjectResourcesAssign, "assign <project-id> --resource=<urn> [--resource=<urn> ...]", "assign one or more resources to a project project", Writer, aliasOpt("a"))
	AddStringSliceFlag(cmdProjectResourcesAssign, doctl.ArgProjectResource, "", []string{}, "resource URNs denoting resources to assign to the project")

	return cmd
}

// RunProjectsList lists Projects.
func RunProjectsList(c *CmdConfig) error {
	ps := c.Projects()
	list, err := ps.List()
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: list})
}

// RunProjectsGet retrieves an existing Project by its identifier. Use "default"
// as an identifier to retrieve your default project.
func RunProjectsGet(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	id := c.Args[0]

	ps := c.Projects()
	p, err := ps.Get(id)
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: do.Projects{*p}})
}

// RunProjectsCreate creates a new Project with a given configuration.
func RunProjectsCreate(c *CmdConfig) error {
	r := new(godo.CreateProjectRequest)
	if err := buildProjectsCreateRequestFromArgs(c, r); err != nil {
		return err
	}

	ps := c.Projects()
	p, err := ps.Create(r)
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: do.Projects{*p}})
}

// RunProjectsUpdate updates an existing Project with a given configuration.
func RunProjectsUpdate(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	id := c.Args[0]

	r := new(godo.UpdateProjectRequest)
	if err := buildProjectsUpdateRequestFromArgs(c, r); err != nil {
		return err
	}

	ps := c.Projects()
	p, err := ps.Update(id, r)
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: do.Projects{*p}})
}

// RunProjectsDelete deletes a Project with a given configuration.
func RunProjectsDelete(c *CmdConfig) error {
	if len(c.Args) < 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}

	force, err := c.Doit.GetBool(c.NS, doctl.ArgForce)
	if err != nil {
		return err
	}

	ps := c.Projects()
	var suffix string
	if len(c.Args) != 1 {
		suffix = "s"
	}
	if force || AskForConfirm(fmt.Sprintf("delete %d project%s", len(c.Args), suffix)) == nil {
		for _, id := range c.Args {
			if err := ps.Delete(id); err != nil {
				return err
			}
		}

		return nil
	}

	return fmt.Errorf("operation aborted")
}

// RunProjectResourcesList lists the Projects.
func RunProjectResourcesList(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	id := c.Args[0]

	ps := c.Projects()
	list, err := ps.ListResources(id)
	if err != nil {
		return err
	}

	return c.Display(&displayers.ProjectResource{ProjectResources: list})
}

// RunProjectResourcesGet retrieves a Project Resource.
func RunProjectResourcesGet(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	urn := c.Args[0]

	parts, isValid := validateURN(urn)
	if !isValid {
		return doctl.NewInvalidURNErr(urn)
	}

	c.Args = []string{parts[2]}
	switch parts[1] {
	case "droplet":
		return RunDropletGet(c)
	case "floatingip":
		return RunFloatingIPGet(c)
	case "loadbalancer":
		return RunLoadBalancerGet(c)
	case "domain":
		return RunDomainGet(c)
	case "volume":
		return RunVolumeGet(c)
	default:
		return fmt.Errorf("%q is an invalid resource type, consult the documentation", parts[1])
	}
}

// RunProjectResourcesAssign assigns a Project Resource.
func RunProjectResourcesAssign(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	projectUUID := c.Args[0]

	urns, err := c.Doit.GetStringSlice(c.NS, doctl.ArgProjectResource)
	if err != nil {
		return err
	}

	ps := c.Projects()
	list, err := ps.AssignResources(projectUUID, urns)
	if err != nil {
		return err
	}

	return c.Display(&displayers.ProjectResource{ProjectResources: list})
}

func validateURN(urn string) ([]string, bool) {
	parts := strings.Split(urn, ":")
	if len(parts) != 3 {
		return nil, false
	}

	if parts[0] != "do" {
		return nil, false
	}

	if strings.TrimSpace(parts[1]) == "" {
		return nil, false
	}

	if strings.TrimSpace(parts[2]) == "" {
		return nil, false
	}

	return parts, true
}

func buildProjectsCreateRequestFromArgs(c *CmdConfig, r *godo.CreateProjectRequest) error {
	name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName)
	if err != nil {
		return err
	}
	r.Name = name

	purpose, err := c.Doit.GetString(c.NS, doctl.ArgProjectPurpose)
	if err != nil {
		return err
	}
	r.Purpose = purpose

	description, err := c.Doit.GetString(c.NS, doctl.ArgProjectDescription)
	if err != nil {
		return err
	}
	r.Description = description

	environment, err := c.Doit.GetString(c.NS, doctl.ArgProjectEnvironment)
	if err != nil {
		return err
	}
	r.Environment = environment

	return nil
}

func buildProjectsUpdateRequestFromArgs(c *CmdConfig, r *godo.UpdateProjectRequest) error {
	if c.Doit.IsSet(doctl.ArgProjectName) {
		name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName)
		if err != nil {
			return err
		}
		r.Name = name
	}

	if c.Doit.IsSet(doctl.ArgProjectPurpose) {
		purpose, err := c.Doit.GetString(c.NS, doctl.ArgProjectPurpose)
		if err != nil {
			return err
		}
		r.Purpose = purpose
	}

	if c.Doit.IsSet(doctl.ArgProjectDescription) {
		description, err := c.Doit.GetString(c.NS, doctl.ArgProjectDescription)
		if err != nil {
			return err
		}
		r.Description = description
	}

	if c.Doit.IsSet(doctl.ArgProjectEnvironment) {
		environment, err := c.Doit.GetString(c.NS, doctl.ArgProjectEnvironment)
		if err != nil {
			return err
		}
		r.Environment = environment
	}

	if c.Doit.IsSet(doctl.ArgProjectIsDefault) {
		isDefault, err := c.Doit.GetBool(c.NS, doctl.ArgProjectIsDefault)
		if err != nil {
			return err
		}
		r.IsDefault = isDefault
	}

	return nil
}
<commit_msg>Fix typo in project resources command<commit_after>/*
Copyright 2018 The Doctl Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
	http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package commands

import (
	"fmt"
	"strings"

	"github.com/digitalocean/doctl"
	"github.com/digitalocean/doctl/commands/displayers"
	"github.com/digitalocean/doctl/do"
	"github.com/digitalocean/godo"
	"github.com/spf13/cobra"
)

// Projects creates the projects commands hierarchy.
func Projects() *Command {
	cmd := &Command{
		Command: &cobra.Command{
			Use:   "projects",
			Short: "projects commands",
			Long:  "projects commands are for creating and managing projects",
		},
	}

	CmdBuilder(cmd, RunProjectsList, "list", "list projects", Writer, aliasOpt("ls"), displayerType(&displayers.Project{}))
	CmdBuilder(cmd, RunProjectsGet, "get <id>", "get a project; use \"default\" as ID to get default project", Writer, aliasOpt("g"), displayerType(&displayers.Project{}))

	cmdProjectsCreate := CmdBuilder(cmd, RunProjectsCreate, "create", "create project", Writer, aliasOpt("c"), displayerType(&displayers.Project{}))
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectName, "", "", "project name", requiredOpt())
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectPurpose, "", "", "project purpose", requiredOpt())
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectDescription, "", "", "a description of your project")
	AddStringFlag(cmdProjectsCreate, doctl.ArgProjectEnvironment, "", "", "the environment in which your project resides. Should be one of 'Development', 'Staging', 'Production'.")

	cmdProjectsUpdate := CmdBuilder(cmd, RunProjectsUpdate, "update <id>", "update project; use \"default\" as ID to update the default project", Writer, aliasOpt("u"), displayerType(&displayers.Project{}))
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectName, "", "", "project name")
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectPurpose, "", "", "project purpose")
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectDescription, "", "", "a description of your project")
	AddStringFlag(cmdProjectsUpdate, doctl.ArgProjectEnvironment, "", "", "the environment in which your project resides. Should be one of 'Development', 'Staging', 'Production'.")
	AddBoolFlag(cmdProjectsUpdate, doctl.ArgProjectIsDefault, "", false, "set the specified project as your default project")

	cmdProjectsDelete := CmdBuilder(cmd, RunProjectsDelete, "delete <id> [<id> ...]", "delete project", Writer, aliasOpt("d", "rm"))
	AddBoolFlag(cmdProjectsDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Force project delete")

	cmd.AddCommand(ProjectResourcesCmd())

	return cmd
}

// ProjectResourcesCmd creates the project resources commands hierarchy.
func ProjectResourcesCmd() *Command {
	cmd := &Command{
		Command: &cobra.Command{
			Use:   "resources",
			Short: "project resources commands",
			Long:  "project resources commands are for assigning and listing resources in projects",
		},
	}
	CmdBuilder(cmd, RunProjectResourcesList, "list <project-id>", "list project resources", Writer, aliasOpt("ls"), displayerType(&displayers.ProjectResource{}))
	CmdBuilder(cmd, RunProjectResourcesGet, "get <urn>", "get a project resource by its URN", Writer, aliasOpt("g"))

	cmdProjectResourcesAssign := CmdBuilder(cmd, RunProjectResourcesAssign, "assign <project-id> --resource=<urn> [--resource=<urn> ...]", "assign one or more resources to a project", Writer, aliasOpt("a"))
	AddStringSliceFlag(cmdProjectResourcesAssign, doctl.ArgProjectResource, "", []string{}, "resource URNs denoting resources to assign to the project")

	return cmd
}

// RunProjectsList lists Projects.
func RunProjectsList(c *CmdConfig) error {
	ps := c.Projects()
	list, err := ps.List()
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: list})
}

// RunProjectsGet retrieves an existing Project by its identifier. Use "default"
// as an identifier to retrieve your default project.
func RunProjectsGet(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	id := c.Args[0]

	ps := c.Projects()
	p, err := ps.Get(id)
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: do.Projects{*p}})
}

// RunProjectsCreate creates a new Project with a given configuration.
func RunProjectsCreate(c *CmdConfig) error {
	r := new(godo.CreateProjectRequest)
	if err := buildProjectsCreateRequestFromArgs(c, r); err != nil {
		return err
	}

	ps := c.Projects()
	p, err := ps.Create(r)
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: do.Projects{*p}})
}

// RunProjectsUpdate updates an existing Project with a given configuration.
func RunProjectsUpdate(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	id := c.Args[0]

	r := new(godo.UpdateProjectRequest)
	if err := buildProjectsUpdateRequestFromArgs(c, r); err != nil {
		return err
	}

	ps := c.Projects()
	p, err := ps.Update(id, r)
	if err != nil {
		return err
	}

	return c.Display(&displayers.Project{Projects: do.Projects{*p}})
}

// RunProjectsDelete deletes a Project with a given configuration.
func RunProjectsDelete(c *CmdConfig) error {
	if len(c.Args) < 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}

	force, err := c.Doit.GetBool(c.NS, doctl.ArgForce)
	if err != nil {
		return err
	}

	ps := c.Projects()
	var suffix string
	if len(c.Args) != 1 {
		suffix = "s"
	}
	if force || AskForConfirm(fmt.Sprintf("delete %d project%s", len(c.Args), suffix)) == nil {
		for _, id := range c.Args {
			if err := ps.Delete(id); err != nil {
				return err
			}
		}

		return nil
	}

	return fmt.Errorf("operation aborted")
}

// RunProjectResourcesList lists the Projects.
func RunProjectResourcesList(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	id := c.Args[0]

	ps := c.Projects()
	list, err := ps.ListResources(id)
	if err != nil {
		return err
	}

	return c.Display(&displayers.ProjectResource{ProjectResources: list})
}

// RunProjectResourcesGet retrieves a Project Resource.
func RunProjectResourcesGet(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	urn := c.Args[0]

	parts, isValid := validateURN(urn)
	if !isValid {
		return doctl.NewInvalidURNErr(urn)
	}

	c.Args = []string{parts[2]}
	switch parts[1] {
	case "droplet":
		return RunDropletGet(c)
	case "floatingip":
		return RunFloatingIPGet(c)
	case "loadbalancer":
		return RunLoadBalancerGet(c)
	case "domain":
		return RunDomainGet(c)
	case "volume":
		return RunVolumeGet(c)
	default:
		return fmt.Errorf("%q is an invalid resource type, consult the documentation", parts[1])
	}
}

// RunProjectResourcesAssign assigns a Project Resource.
func RunProjectResourcesAssign(c *CmdConfig) error {
	if len(c.Args) != 1 {
		return doctl.NewMissingArgsErr(c.NS)
	}
	projectUUID := c.Args[0]

	urns, err := c.Doit.GetStringSlice(c.NS, doctl.ArgProjectResource)
	if err != nil {
		return err
	}

	ps := c.Projects()
	list, err := ps.AssignResources(projectUUID, urns)
	if err != nil {
		return err
	}

	return c.Display(&displayers.ProjectResource{ProjectResources: list})
}

func validateURN(urn string) ([]string, bool) {
	parts := strings.Split(urn, ":")
	if len(parts) != 3 {
		return nil, false
	}

	if parts[0] != "do" {
		return nil, false
	}

	if strings.TrimSpace(parts[1]) == "" {
		return nil, false
	}

	if strings.TrimSpace(parts[2]) == "" {
		return nil, false
	}

	return parts, true
}

func buildProjectsCreateRequestFromArgs(c *CmdConfig, r *godo.CreateProjectRequest) error {
	name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName)
	if err != nil {
		return err
	}
	r.Name = name

	purpose, err := c.Doit.GetString(c.NS, doctl.ArgProjectPurpose)
	if err != nil {
		return err
	}
	r.Purpose = purpose

	description, err := c.Doit.GetString(c.NS, doctl.ArgProjectDescription)
	if err != nil {
		return err
	}
	r.Description = description

	environment, err := c.Doit.GetString(c.NS, doctl.ArgProjectEnvironment)
	if err != nil {
		return err
	}
	r.Environment = environment

	return nil
}

func buildProjectsUpdateRequestFromArgs(c *CmdConfig, r *godo.UpdateProjectRequest) error {
	if c.Doit.IsSet(doctl.ArgProjectName) {
		name, err := c.Doit.GetString(c.NS, doctl.ArgProjectName)
		if err != nil {
			return err
		}
		r.Name = name
	}

	if c.Doit.IsSet(doctl.ArgProjectPurpose) {
		purpose, err := c.Doit.GetString(c.NS, doctl.ArgProjectPurpose)
		if err != nil {
			return err
		}
		r.Purpose = purpose
	}

	if c.Doit.IsSet(doctl.ArgProjectDescription) {
		description, err := c.Doit.GetString(c.NS, doctl.ArgProjectDescription)
		if err != nil {
			return err
		}
		r.Description = description
	}

	if c.Doit.IsSet(doctl.ArgProjectEnvironment) {
		environment, err := c.Doit.GetString(c.NS, doctl.ArgProjectEnvironment)
		if err != nil {
			return err
		}
		r.Environment = environment
	}

	if c.Doit.IsSet(doctl.ArgProjectIsDefault) {
		isDefault, err := c.Doit.GetBool(c.NS, doctl.ArgProjectIsDefault)
		if err != nil {
			return err
		}
		r.IsDefault = isDefault
	}

	return nil
}
<|endoftext|>


<commit_before>package chatroom

import (
	"fmt"
)

// TopicChan includes chatroom channel, the channel pass the returned value from topic.
type TopicChan struct {
	Chatroom roomInternal
	Return   chan bool
}

// A DidTalk means whether the Topic talks with user. A Topic function must return this type value.
type DidTalk bool

// Topic type is the function express a bunch of flow in chattting. Pass slice of this to New(), and the function called them in order. If one of them returns true, the loop breaks.
type Topic func(Room) DidTalk

func (cr Chatroom) talk(chatroom roomInternal) {
	topicChans := []TopicChan{}
	for _, topic := range cr.topics {
		topicChan := loopTopic(topic, chatroom)
		topicChans = append(topicChans, topicChan)
	}
	middleChatroom, clearPool, broadcastPool := poolMessages(chatroom)
	changeDestTopicTo := distributeMessage(middleChatroom)
	go controller(topicChans, changeDestTopicTo, broadcastPool, clearPool)
}

func controller(topicChans []TopicChan, changeDestTopicTo chan roomInternal, broadcastPool chan bool, clearPool chan bool) {
	for {
		for i, topicChan := range topicChans {
			changeDestTopicTo <- topicChan.Chatroom
			if i > 0 { // for the start time.
				broadcastPool <- true
			}
			didTalk := <-topicChan.Return
			if didTalk {
				clearPool <- true
				break
			}
		}
		clearPool <- true
	}
}

// This pipe stores messages from user with flowing next Chatroom(middleChatroom). And this provides functions, clearPool and broadcastPool. This is used in controller().
func poolMessages(chatroom roomInternal) (roomInternal, chan bool, chan bool) {
	middleChatroom := roomInternal{
		in:  make(chan interface{}),
		out: chatroom.out,
	}
	clearPool := make(chan bool)
	broadcastPool := make(chan bool)

	go func(chatroom roomInternal, middleChatroom roomInternal, clearPool <-chan bool, broadcastPool <-chan bool) {
		var pool []interface{}
		for {
			select {
			case message := <-chatroom.in:
				pool = append(pool, message)
				middleChatroom.in <- message

			case <-clearPool:
				pool = pool[:0]

			case <-broadcastPool:
				for _, message := range pool {
					middleChatroom.in <- message
				}
			}
		}
	}(chatroom, middleChatroom, clearPool, broadcastPool)

	return middleChatroom, clearPool, broadcastPool
}

// distributeMessage pass message from chatroom to chatroom. The chatroom of destination will change as needed, changed by value of channel, changeDestTopicTo.
func distributeMessage(middleChatroom roomInternal) chan roomInternal {
	changeDestTopicTo := make(chan roomInternal)

	go func(middleChatroom roomInternal, changeDestTopicTo <-chan roomInternal) {
		var dest roomInternal
		dest = <-changeDestTopicTo
		for {
			select {
			case message := <-middleChatroom.in:
				if dest == (roomInternal{}) {
					fmt.Println("Error: the destination chatroom is not set.")
					break
				}
				dest.in <- message

			case _dest := <-changeDestTopicTo:
				dest = _dest
			}
		}
	}(middleChatroom, changeDestTopicTo)

	return changeDestTopicTo
}

// loopTopic just loops topic.
func loopTopic(topic Topic, chatroom roomInternal) TopicChan {
	topicChan := TopicChan{
		Chatroom: roomInternal{
			in:  make(chan interface{}),
			out: chatroom.out,
		},
		Return: make(chan bool),
	}

	go func(topic Topic, topicChan TopicChan) {
		for {
			topicChan.Return <- bool(topic(topicChan.Chatroom))
		}
	}(topic, topicChan)

	return topicChan
}
<commit_msg>[Modify] Make TopicChan type private.<commit_after>package chatroom

import (
	"fmt"
)

// topicChan includes chatroom channel, the channel pass the returned value from topic.
type topicChan struct {
	Chatroom roomInternal
	Return   chan bool
}

// A DidTalk means whether the Topic talks with user. A Topic function must return this type value.
type DidTalk bool

// Topic type is the function express a bunch of flow in chattting. Pass slice of this to New(), and the function called them in order. If one of them returns true, the loop breaks.
type Topic func(Room) DidTalk

func (cr Chatroom) talk(chatroom roomInternal) {
	topicChans := []topicChan{}
	for _, topic := range cr.topics {
		topicChan := loopTopic(topic, chatroom)
		topicChans = append(topicChans, topicChan)
	}
	middleChatroom, clearPool, broadcastPool := poolMessages(chatroom)
	changeDestTopicTo := distributeMessage(middleChatroom)
	go controller(topicChans, changeDestTopicTo, broadcastPool, clearPool)
}

func controller(topicChans []topicChan, changeDestTopicTo chan roomInternal, broadcastPool chan bool, clearPool chan bool) {
	for {
		for i, topicChan := range topicChans {
			changeDestTopicTo <- topicChan.Chatroom
			if i > 0 { // for the start time.
				broadcastPool <- true
			}
			didTalk := <-topicChan.Return
			if didTalk {
				clearPool <- true
				break
			}
		}
		clearPool <- true
	}
}

// This pipe stores messages from user with flowing next Chatroom(middleChatroom). And this provides functions, clearPool and broadcastPool. This is used in controller().
func poolMessages(chatroom roomInternal) (roomInternal, chan bool, chan bool) {
	middleChatroom := roomInternal{
		in:  make(chan interface{}),
		out: chatroom.out,
	}
	clearPool := make(chan bool)
	broadcastPool := make(chan bool)

	go func(chatroom roomInternal, middleChatroom roomInternal, clearPool <-chan bool, broadcastPool <-chan bool) {
		var pool []interface{}
		for {
			select {
			case message := <-chatroom.in:
				pool = append(pool, message)
				middleChatroom.in <- message

			case <-clearPool:
				pool = pool[:0]

			case <-broadcastPool:
				for _, message := range pool {
					middleChatroom.in <- message
				}
			}
		}
	}(chatroom, middleChatroom, clearPool, broadcastPool)

	return middleChatroom, clearPool, broadcastPool
}

// distributeMessage pass message from chatroom to chatroom. The chatroom of destination will change as needed, changed by value of channel, changeDestTopicTo.
func distributeMessage(middleChatroom roomInternal) chan roomInternal {
	changeDestTopicTo := make(chan roomInternal)

	go func(middleChatroom roomInternal, changeDestTopicTo <-chan roomInternal) {
		var dest roomInternal
		dest = <-changeDestTopicTo
		for {
			select {
			case message := <-middleChatroom.in:
				if dest == (roomInternal{}) {
					fmt.Println("Error: the destination chatroom is not set.")
					break
				}
				dest.in <- message

			case _dest := <-changeDestTopicTo:
				dest = _dest
			}
		}
	}(middleChatroom, changeDestTopicTo)

	return changeDestTopicTo
}

// loopTopic just loops topic.
func loopTopic(topic Topic, chatroom roomInternal) topicChan {
	tc := topicChan{
		Chatroom: roomInternal{
			in:  make(chan interface{}),
			out: chatroom.out,
		},
		Return: make(chan bool),
	}

	go func(topic Topic, tc topicChan) {
		for {
			tc.Return <- bool(topic(tc.Chatroom))
		}
	}(topic, tc)

	return tc
}
<|endoftext|>


<commit_before>package runtime

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"net/http"

	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
)

const MIMEWILDCARD = "*"

var inboundDefaultMarshaler = &JSONBuiltin{}
var outboundDefaultMarshaler = &JSONBuiltin{}
var contentTypeHeader = http.CanonicalHeaderKey("Content-Type")

// Get the inbound/outbound marshalers for this request. Checks the registry on the ServeMux for
// the MIME type set by the Content-Type header.
// If it isn't set (or the request Content-Type is empty), checks for "*".
// If that isn't set, uses the ServerMux's InboundMarshaler/OutboundMarshaler.
// If there are multiple Content-Type headers set, choose the first one that it can
// exactly match in the registry. Otherwise, follows the above logic for "*"/InboundMarshaler/OutboundMarshaler.
func MarshalerForRequest(mux *ServeMux, r *http.Request) (inbound Marshaler, outbound Marshaler) {

	inbound = nil
	outbound = nil

	headerVals := r.Header[contentTypeHeader]

	for _, val := range headerVals {
		if mux.MIMERegistry != nil && mux.MIMERegistry.mimeMap != nil {
			if m, ok := mux.MIMERegistry.mimeMap[val]; ok {
				if inbound == nil && m.inbound != nil {
					inbound = m.inbound
				}
				if outbound == nil && m.outbound != nil {
					outbound = m.outbound
				}
			}
		} else {
			//Nil mimeMap, no need to bother checking for MIMEWILDCARD
			if mux.InboundMarshaler == nil {
				//Its nil, use our default
				inbound = inboundDefaultMarshaler
			} else {
				inbound = mux.InboundMarshaler
			}

			if mux.OutboundMarshaler == nil {
				//Its nil, use our default
				outbound = outboundDefaultMarshaler
			} else {
				outbound = mux.OutboundMarshaler
			}
		}

		if inbound != nil && outbound != nil {
			//Got them both, return
			return inbound, outbound
		}
	}

	if mux.MIMERegistry != nil && mux.MIMERegistry.mimeMap != nil {
		if m, ok := mux.MIMERegistry.mimeMap[MIMEWILDCARD]; ok {
			if inbound == nil && m.inbound != nil {
				inbound = m.inbound
			}
			if outbound == nil && m.outbound != nil {
				outbound = m.outbound
			}
		}
	}

	//Haven't gotten anywhere with any of the headers or MIMEWILDCARD
	//Try to use the mux, otherwise use our default
	if inbound == nil {
		if mux.InboundMarshaler == nil {
			//Its nil, use our default
			inbound = inboundDefaultMarshaler
		} else {
			inbound = mux.InboundMarshaler
		}
	}

	if outbound == nil {
		if mux.OutboundMarshaler == nil {
			//Its nil, use our default
			outbound = outboundDefaultMarshaler
		} else {
			outbound = mux.OutboundMarshaler
		}
	}

	return inbound, outbound

}

type MarshalerMIMERegistry struct {
	mimeMap map[string]*mimeMarshaler
}

type mimeMarshaler struct {
	inbound  Marshaler
	outbound Marshaler
}

// Add an inbound and outbund marshaler for a case-sensitive MIME type string ("*" to match any MIME type).
// Inbound is the marshaler that is used when marshaling inbound requests from the client.
// Outbound is the marshaler that is used when marshaling outbound responses to the client.
func (m *MarshalerMIMERegistry) AddMarshaler(mime string, inbound Marshaler, outbound Marshaler) {

	if len(mime) == 0 {
		panic("Mime can't be an empty string")
	}

	m.mimeMap[mime] = &mimeMarshaler{
		inbound:  inbound,
		outbound: outbound,
	}

}

// Add an inbound marshaler for a case-sensitive MIME type string ("*" to match any MIME type).
// Inbound is the marshaler that is used when marshaling inbound requests from the client.
func (m *MarshalerMIMERegistry) AddInboundMarshaler(mime string, inbound Marshaler) {

	if len(mime) == 0 {
		panic("Mime can't be an empty string")
	}

	if _, ok := m.mimeMap[mime]; ok {
		//Already have this mime, just change inbound
		m.mimeMap[mime].inbound = inbound
	} else {
		m.mimeMap[mime] = &mimeMarshaler{
			inbound:  inbound,
			outbound: nil,
		}
	}

}

// Add an outbund marshaler for a case-sensitive MIME type string ("*" to match any MIME type).
// Outbound is the marshaler that is used when marshaling outbound responses to the client.
func (m *MarshalerMIMERegistry) AddOutboundMarshaler(mime string, outbound Marshaler) {
	mime = http.CanonicalHeaderKey(mime)
	if len(mime) == 0 {
		panic("Mime can't be an empty string")
	}

	if _, ok := m.mimeMap[mime]; ok {
		//Already have this mime, just change outbound
		m.mimeMap[mime].outbound = outbound
	} else {
		m.mimeMap[mime] = &mimeMarshaler{
			inbound:  nil,
			outbound: outbound,
		}
	}

}

// Create a new MIME marshaler registry. Allows for a mapping of case-sensitive
// Content-Type MIME type string to runtime.Marshaler interfaces.
// For example, you could allow the client to specify the use of the runtime.JSONPb marshaler
// with a "applicaton/jsonpb" Content-Type and the use of the runtime.JSONBuiltin marshaler
// with a "application/json" Content-Type.
// "*" can be used to match any Content-Type.
// This can be attached to a ServerMux with the MIMERegistry option.
func NewMarshalerMIMERegistry() *MarshalerMIMERegistry {
	return &MarshalerMIMERegistry{
		mimeMap: make(map[string]*mimeMarshaler),
	}
}

type Marshaler interface {
	Marshal(v interface{}) ([]byte, error)
	Unmarshal(data []byte, v interface{}) error
	NewDecoder(r io.Reader) Decoder
	NewEncoder(w io.Writer) Encoder
	ContentType() string
}

type Decoder interface {
	Decode(v interface{}) error
}

type Encoder interface {
	Encode(v interface{}) error
}

type JSONBuiltin struct{}

func (*JSONBuiltin) ContentType() string {
	return "application/json"
}

func (j *JSONBuiltin) Marshal(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}

func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}

func (j *JSONBuiltin) NewDecoder(r io.Reader) Decoder {
	return json.NewDecoder(r)
}

func (j *JSONBuiltin) NewEncoder(w io.Writer) Encoder {
	return json.NewEncoder(w)
}

type JSONPb struct {
	// Whether to render enum values as integers, as opposed to string values.
	EnumsAsInts bool

	// Whether to render fields with zero values.
	EmitDefaults bool

	// A string to indent each level by. The presence of this field will
	// also cause a space to appear between the field separator and
	// value, and for newlines to be appear between fields and array
	// elements.
	Indent string

	// Whether to use the original (.proto) name for fields.
	OrigName bool
}

func (*JSONPb) ContentType() string {
	return "application/json"
}

func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
	m := &jsonpb.Marshaler{
		EnumsAsInts:  j.EnumsAsInts,
		EmitDefaults: j.EmitDefaults,
		Indent:       j.Indent,
		OrigName:     j.OrigName,
	}
	if p, ok := v.(proto.Message); ok {
		var buf bytes.Buffer
		if err := m.Marshal(&buf, p); err != nil {
			return nil, err
		}
		return buf.Bytes(), nil
	} else {
		return nil, errors.New("Interface is not proto.Message")
	}

}

func (j *JSONPb) Unmarshal(data []byte, v interface{}) error {
	r := bytes.NewReader(data)
	if p, ok := v.(proto.Message); ok {
		return jsonpb.Unmarshal(r, p)
	} else {
		return errors.New("Interface is not proto.Message")
	}
}

func (j *JSONPb) NewDecoder(r io.Reader) Decoder {
	return &JSONPbDecoder{decoder: json.NewDecoder(r)}
}

func (j *JSONPb) NewEncoder(w io.Writer) Encoder {
	m := &jsonpb.Marshaler{
		EnumsAsInts:  j.EnumsAsInts,
		EmitDefaults: j.EmitDefaults,
		Indent:       j.Indent,
		OrigName:     j.OrigName,
	}

	return &JSONPbEncoder{
		marshal: m,
		writer:  w,
	}
}

type JSONPbDecoder struct {
	decoder *json.Decoder
}

func (j *JSONPbDecoder) Decode(v interface{}) error {
	if p, ok := v.(proto.Message); ok {
		return jsonpb.UnmarshalNext(j.decoder, p)
	} else {
		return errors.New("Interface is not proto.Message")
	}
}

type JSONPbEncoder struct {
	marshal *jsonpb.Marshaler
	writer  io.Writer
}

func (j *JSONPbEncoder) Encode(v interface{}) error {
	if p, ok := v.(proto.Message); ok {
		return j.marshal.Marshal(j.writer, p)
	} else {
		return errors.New("Interface is not proto.Message")
	}
}
<commit_msg>Fix style and lint errors<commit_after>package runtime

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"net/http"

	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
)

const mimeWildcard = "*"

var (
	inboundDefaultMarshaler  = new(JSONBuiltin)
	outboundDefaultMarshaler = new(JSONBuiltin)

	contentTypeHeader = http.CanonicalHeaderKey("Content-Type")
)

// MarshalerForRequest returns the inbound/outbound marshalers for this request.
// It checks the registry on the ServeMux for the MIME type set by the Content-Type header.
// If it isn't set (or the request Content-Type is empty), checks for "*".
// If that isn't set, uses the ServerMux's InboundMarshaler/OutboundMarshaler.
// If there are multiple Content-Type headers set, choose the first one that it can
// exactly match in the registry.
// Otherwise, it follows the above logic for "*"/InboundMarshaler/OutboundMarshaler.
func MarshalerForRequest(mux *ServeMux, r *http.Request) (inbound Marshaler, outbound Marshaler) {
	inbound = nil
	outbound = nil
	headerVals := r.Header[contentTypeHeader]

	for _, val := range headerVals {
		if mux.MIMERegistry != nil && mux.MIMERegistry.mimeMap != nil {
			if m, ok := mux.MIMERegistry.mimeMap[val]; ok {
				if inbound == nil && m.inbound != nil {
					inbound = m.inbound
				}
				if outbound == nil && m.outbound != nil {
					outbound = m.outbound
				}
			}
		} else {
			//Nil mimeMap, no need to bother checking for mimeWildcard
			if mux.InboundMarshaler == nil {
				//Its nil, use our default
				inbound = inboundDefaultMarshaler
			} else {
				inbound = mux.InboundMarshaler
			}

			if mux.OutboundMarshaler == nil {
				//Its nil, use our default
				outbound = outboundDefaultMarshaler
			} else {
				outbound = mux.OutboundMarshaler
			}
		}

		if inbound != nil && outbound != nil {
			//Got them both, return
			return inbound, outbound
		}
	}

	if mux.MIMERegistry != nil && mux.MIMERegistry.mimeMap != nil {
		if m, ok := mux.MIMERegistry.mimeMap[mimeWildcard]; ok {
			if inbound == nil && m.inbound != nil {
				inbound = m.inbound
			}
			if outbound == nil && m.outbound != nil {
				outbound = m.outbound
			}
		}
	}

	//Haven't gotten anywhere with any of the headers or mimeWildcard
	//Try to use the mux, otherwise use our default
	if inbound == nil {
		if mux.InboundMarshaler == nil {
			//Its nil, use our default
			inbound = inboundDefaultMarshaler
		} else {
			inbound = mux.InboundMarshaler
		}
	}

	if outbound == nil {
		if mux.OutboundMarshaler == nil {
			//Its nil, use our default
			outbound = outboundDefaultMarshaler
		} else {
			outbound = mux.OutboundMarshaler
		}
	}

	return inbound, outbound

}

// MarshalerMIMERegistry keeps a mapping from MIME types to mimeMarshalers.
type MarshalerMIMERegistry struct {
	mimeMap map[string]*mimeMarshaler
}

type mimeMarshaler struct {
	inbound  Marshaler
	outbound Marshaler
}

// AddMarshaler adds an inbound and outbund marshaler for a case-sensitive MIME type string ("*" to match any MIME type).
// Inbound is the marshaler that is used when marshaling inbound requests from the client.
// Outbound is the marshaler that is used when marshaling outbound responses to the client.
func (m *MarshalerMIMERegistry) AddMarshaler(mime string, inbound, outbound Marshaler) {

	if len(mime) == 0 {
		panic("Mime can't be an empty string")
	}

	m.mimeMap[mime] = &mimeMarshaler{
		inbound:  inbound,
		outbound: outbound,
	}

}

// AddInboundMarshaler adds an inbound marshaler for a case-sensitive MIME type string ("*" to match any MIME type).
// Inbound is the marshaler that is used when marshaling inbound requests from the client.
func (m *MarshalerMIMERegistry) AddInboundMarshaler(mime string, inbound Marshaler) {

	if len(mime) == 0 {
		panic("Mime can't be an empty string")
	}

	if _, ok := m.mimeMap[mime]; ok {
		//Already have this mime, just change inbound
		m.mimeMap[mime].inbound = inbound
	} else {
		m.mimeMap[mime] = &mimeMarshaler{
			inbound:  inbound,
			outbound: nil,
		}
	}

}

// AddOutboundMarshaler adds an outbund marshaler for a case-sensitive MIME type string ("*" to match any MIME type).
// Outbound is the marshaler that is used when marshaling outbound responses to the client.
func (m *MarshalerMIMERegistry) AddOutboundMarshaler(mime string, outbound Marshaler) {
	mime = http.CanonicalHeaderKey(mime)
	if len(mime) == 0 {
		panic("Mime can't be an empty string")
	}

	if _, ok := m.mimeMap[mime]; ok {
		//Already have this mime, just change outbound
		m.mimeMap[mime].outbound = outbound
	} else {
		m.mimeMap[mime] = &mimeMarshaler{
			inbound:  nil,
			outbound: outbound,
		}
	}

}

// NewMarshalerMIMERegistry returns a new registry of marshalers.
// It allows for a mapping of case-sensitive Content-Type MIME type string to runtime.Marshaler interfaces.
//
// For example, you could allow the client to specify the use of the runtime.JSONPb marshaler
// with a "applicaton/jsonpb" Content-Type and the use of the runtime.JSONBuiltin marshaler
// with a "application/json" Content-Type.
// "*" can be used to match any Content-Type.
// This can be attached to a ServerMux with the MIMERegistry option.
func NewMarshalerMIMERegistry() *MarshalerMIMERegistry {
	return &MarshalerMIMERegistry{
		mimeMap: make(map[string]*mimeMarshaler),
	}
}

// Marshaler defines a conversion between byte sequence and gRPC payloads / fields.
type Marshaler interface {
	// Marshal marshals "v" into byte sequence.
	Marshal(v interface{}) ([]byte, error)
	// Unmarshal unmarshals "data" into "v".
	// "v" must be a pointer value.
	Unmarshal(data []byte, v interface{}) error
	// NewDecoder returns a Decoder which reads byte sequence from "r".
	NewDecoder(r io.Reader) Decoder
	// NewEncoder returns an Encoder which writes bytes sequence into "w".
	NewEncoder(w io.Writer) Encoder
	// ContentType returns the Content-Type which this marshaler is responsible for.
	ContentType() string
}

// Decoder decodes a byte sequence
type Decoder interface {
	Decode(v interface{}) error
}

// Encoder encodes gRPC payloads / fields into byte sequence.
type Encoder interface {
	Encode(v interface{}) error
}

// JSONBuiltin is a Marshaler which marshals/unmarshals into/from JSON
// with the standard "encoding/json" package of Golang.
// Although it is generally faster for simple proto messages than JSONPb,
// it does not support advanced features of protobuf, e.g. map, oneof, ....
type JSONBuiltin struct{}

// ContentType always Returns "application/json".
func (*JSONBuiltin) ContentType() string {
	return "application/json"
}

// Marshal marshals "v" into JSON
func (j *JSONBuiltin) Marshal(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}

// Unmarshal unmarshals JSON data into "v".
func (j *JSONBuiltin) Unmarshal(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}

// NewDecoder returns a Decoder which reads JSON stream from "r".
func (j *JSONBuiltin) NewDecoder(r io.Reader) Decoder {
	return json.NewDecoder(r)
}

// NewEncoder returns an Encoder which writes JSON stream into "w".
func (j *JSONBuiltin) NewEncoder(w io.Writer) Encoder {
	return json.NewEncoder(w)
}

// JSONPb is a Marshaler which marshals/unmarshals into/from JSON
// with the "github.com/golang/protobuf/jsonpb".
// It supports fully functionality of protobuf unlike JSONBuiltin.
type JSONPb struct {
	// Whether to render enum values as integers, as opposed to string values.
	EnumsAsInts bool

	// Whether to render fields with zero values.
	EmitDefaults bool

	// A string to indent each level by. The presence of this field will
	// also cause a space to appear between the field separator and
	// value, and for newlines to be appear between fields and array
	// elements.
	Indent string

	// Whether to use the original (.proto) name for fields.
	OrigName bool
}

// ContentType always returns "application/json".
func (*JSONPb) ContentType() string {
	return "application/json"
}

// Marshal marshals "v" into JSON
// Currently it can marshal only proto.Message.
// TODO(yugui) Support fields of primitive types in a message.
func (j *JSONPb) Marshal(v interface{}) ([]byte, error) {
	m := &jsonpb.Marshaler{
		EnumsAsInts:  j.EnumsAsInts,
		EmitDefaults: j.EmitDefaults,
		Indent:       j.Indent,
		OrigName:     j.OrigName,
	}
	p, ok := v.(proto.Message)
	if !ok {
		return nil, errors.New("interface is not proto.Message")
	}

	var buf bytes.Buffer
	if err := m.Marshal(&buf, p); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil

}

// Unmarshal unmarshals JSON "data" into "v"
// Currently it can marshal only proto.Message.
// TODO(yugui) Support fields of primitive types in a message.
func (j *JSONPb) Unmarshal(data []byte, v interface{}) error {
	r := bytes.NewReader(data)
	p, ok := v.(proto.Message)
	if !ok {
		return errors.New("interface is not proto.Message")
	}
	return jsonpb.Unmarshal(r, p)
}

// NewDecoder returns a Decoder which reads JSON stream from "r".
func (j *JSONPb) NewDecoder(r io.Reader) Decoder {
	return &jsonPbDecoder{decoder: json.NewDecoder(r)}
}

// NewEncoder returns an Encoder which writes JSON stream into "w".
func (j *JSONPb) NewEncoder(w io.Writer) Encoder {
	m := &jsonpb.Marshaler{
		EnumsAsInts:  j.EnumsAsInts,
		EmitDefaults: j.EmitDefaults,
		Indent:       j.Indent,
		OrigName:     j.OrigName,
	}

	return &jsonPbEncoder{
		marshal: m,
		writer:  w,
	}
}

type jsonPbDecoder struct {
	decoder *json.Decoder
}

func (j *jsonPbDecoder) Decode(v interface{}) error {
	p, ok := v.(proto.Message)
	if !ok {
		return errors.New("interface is not proto.Message")
	}

	return jsonpb.UnmarshalNext(j.decoder, p)
}

type jsonPbEncoder struct {
	marshal *jsonpb.Marshaler
	writer  io.Writer
}

func (j *jsonPbEncoder) Encode(v interface{}) error {
	p, ok := v.(proto.Message)
	if !ok {
		return errors.New("interface is not proto.Message")
	}
	return j.marshal.Marshal(j.writer, p)
}
<|endoftext|>


<commit_before>package controllers

import (
	"github.com/revel/revel"
	"os"
	fpath "path/filepath"
	"strings"
	"syscall"
)

type Static struct {
	*revel.Controller
}

// This method handles requests for files. The supplied prefix may be absolute
// or relative. If the prefix is relative it is assumed to be relative to the
// application directory. The filepath may either be just a file or an
// additional filepath to search for the given file. This response may return
// the following responses in the event of an error or invalid request;
//   403(Forbidden): If the prefix filepath combination results in a directory.
//   404(Not found): If the prefix and filepath combination results in a non-existent file.
//   500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel.
//
// Note that when defining routes in routes/conf the parameters must not have
// spaces around the comma.
//   Bad:  Static.Serve("public/img", "favicon.png")
//   Good: Static.Serve("public/img","favicon.png")
//
// Examples:
// Serving a directory
//   Route (conf/routes):
//     GET /public/{<.*>filepath} Static.Serve("public")
//   Request:
//     public/js/sessvars.js
//   Calls
//     Static.Serve("public","js/sessvars.js")
//
// Serving a file
//   Route (conf/routes):
//     GET /favicon.ico Static.Serve("public/img","favicon.png")
//   Request:
//     favicon.ico
//   Calls:
//     Static.Serve("public/img", "favicon.png")
func (c Static) Serve(prefix, filepath string) revel.Result {
	var basePath string

	if !fpath.IsAbs(prefix) {
		basePath = revel.BasePath
	}

	basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix))
	fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath))
	if !strings.HasPrefix(fname, basePathPrefix) {
		revel.WARN.Printf("Attempted to read file outside of base path: %s", fname)
		return c.NotFound("")
	}

	finfo, err := os.Stat(fname)
	if err != nil {
		if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
		return c.RenderError(err)
	}

	if finfo.Mode().IsDir() {
		revel.WARN.Printf("Attempted directory listing of %s", fname)
		return c.Forbidden("Directory listing not allowed")
	}

	file, err := os.Open(fname)
	if err != nil {
		if os.IsNotExist(err) {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error opening '%s': %s", fname, err)
		return c.RenderError(err)
	}
	return c.RenderFile(file, revel.Inline)
}

// This method allows modules to serve binary files. The parameters are the same
// as Static.Serve with the additional module name pre-pended to the list of
// arguments.
func (c Static) ServeModule(moduleName, prefix, filepath string) revel.Result {
	var basePath string
	for _, module := range revel.Modules {
		if module.Name == moduleName {
			basePath = module.Path
		}
	}

	absPath := fpath.Join(basePath, fpath.FromSlash(prefix))

	return c.Serve(absPath, filepath)
}
<commit_msg>Static module: get prefix from fixed params explicitly<commit_after>package controllers

import (
	"github.com/revel/revel"
	"os"
	fpath "path/filepath"
	"strings"
	"syscall"
)

type Static struct {
	*revel.Controller
}

// This method handles requests for files. The supplied prefix may be absolute
// or relative. If the prefix is relative it is assumed to be relative to the
// application directory. The filepath may either be just a file or an
// additional filepath to search for the given file. This response may return
// the following responses in the event of an error or invalid request;
//   403(Forbidden): If the prefix filepath combination results in a directory.
//   404(Not found): If the prefix and filepath combination results in a non-existent file.
//   500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel.
//
// Note that when defining routes in routes/conf the parameters must not have
// spaces around the comma.
//   Bad:  Static.Serve("public/img", "favicon.png")
//   Good: Static.Serve("public/img","favicon.png")
//
// Examples:
// Serving a directory
//   Route (conf/routes):
//     GET /public/{<.*>filepath} Static.Serve("public")
//   Request:
//     public/js/sessvars.js
//   Calls
//     Static.Serve("public","js/sessvars.js")
//
// Serving a file
//   Route (conf/routes):
//     GET /favicon.ico Static.Serve("public/img","favicon.png")
//   Request:
//     favicon.ico
//   Calls:
//     Static.Serve("public/img", "favicon.png")
func (c Static) Serve(prefix, filepath string) revel.Result {
	// Fix for #503.
	prefix = c.Params.Fixed.Get("prefix")
	if prefix == "" {
		return c.NotFound("")
	}

	var basePath string
	if !fpath.IsAbs(prefix) {
		basePath = revel.BasePath
	}

	basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix))
	fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath))
	if !strings.HasPrefix(fname, basePathPrefix) {
		revel.WARN.Printf("Attempted to read file outside of base path: %s", fname)
		return c.NotFound("")
	}

	finfo, err := os.Stat(fname)
	if err != nil {
		if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
		return c.RenderError(err)
	}

	if finfo.Mode().IsDir() {
		revel.WARN.Printf("Attempted directory listing of %s", fname)
		return c.Forbidden("Directory listing not allowed")
	}

	file, err := os.Open(fname)
	if err != nil {
		if os.IsNotExist(err) {
			revel.WARN.Printf("File not found (%s): %s ", fname, err)
			return c.NotFound("File not found")
		}
		revel.ERROR.Printf("Error opening '%s': %s", fname, err)
		return c.RenderError(err)
	}
	return c.RenderFile(file, revel.Inline)
}

// This method allows modules to serve binary files. The parameters are the same
// as Static.Serve with the additional module name pre-pended to the list of
// arguments.
func (c Static) ServeModule(moduleName, prefix, filepath string) revel.Result {
	// Fix for #503.
	prefix = c.Params.Fixed.Get("prefix")
	if prefix == "" {
		return c.NotFound("")
	}

	var basePath string
	for _, module := range revel.Modules {
		if module.Name == moduleName {
			basePath = module.Path
		}
	}

	absPath := fpath.Join(basePath, fpath.FromSlash(prefix))

	return c.Serve(absPath, filepath)
}
<|endoftext|>


<commit_before>package autopilot

import (
	"bytes"
	"math/big"
	"net"
	"sync/atomic"
	"time"

	"github.com/boltdb/bolt"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcutil"
)

var (
	testSig = &btcec.Signature{
		R: new(big.Int),
		S: new(big.Int),
	}
	_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
	_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)

	chanIDCounter uint64
)

// databaseChannelGraph wraps a channeldb.ChannelGraph instance with the
// necessary API to properly implement the autopilot.ChannelGraph interface.
//
// TODO(roasbeef): move inmpl to main package?
type databaseChannelGraph struct {
	db *channeldb.ChannelGraph
}

// A compile time assertion to ensure databaseChannelGraph meets the
// autopilot.ChannelGraph interface.
var _ ChannelGraph = (*databaseChannelGraph)(nil)

// ChannelGraphFromDatabase returns a instance of the autopilot.ChannelGraph
// backed by a live, open channeldb instance.
func ChannelGraphFromDatabase(db *channeldb.ChannelGraph) ChannelGraph {
	return &databaseChannelGraph{
		db: db,
	}
}

// type dbNode is a wrapper struct around a database transaction an
// channeldb.LightningNode. The wrapper method implement the autopilot.Node
// interface.
type dbNode struct {
	tx *bolt.Tx

	node *channeldb.LightningNode
}

// A compile time assertion to ensure dbNode meets the autopilot.Node
// interface.
var _ Node = (*dbNode)(nil)

// PubKey is the identity public key of the node. This will be used to attempt
// to target a node for channel opening by the main autopilot agent.
//
// NOTE: Part of the autopilot.Node interface.
func (d dbNode) PubKey() *btcec.PublicKey {
	return d.node.PubKey
}

// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (d dbNode) Addrs() []net.Addr {
	return d.node.Addresses
}

// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (d dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
	return d.node.ForEachChannel(d.tx, func(tx *bolt.Tx,
		ei *channeldb.ChannelEdgeInfo, ep, _ *channeldb.ChannelEdgePolicy) error {

		edge := ChannelEdge{
			Channel: Channel{
				ChanID:    lnwire.NewShortChanIDFromInt(ep.ChannelID),
				Capacity:  ei.Capacity,
				FundedAmt: ei.Capacity,
				Node:      NewNodeID(ep.Node.PubKey),
			},
			Peer: dbNode{
				tx:   tx,
				node: ep.Node,
			},
		}

		return cb(edge)
	})
}

// ForEachNode is a higher-order function that should be called once for each
// connected node within the channel graph. If the passed callback returns an
// error, then execution should be terminated.
//
// NOTE: Part of the autopilot.ChannelGraph interface.
func (d *databaseChannelGraph) ForEachNode(cb func(Node) error) error {
	return d.db.ForEachNode(nil, func(tx *bolt.Tx, n *channeldb.LightningNode) error {

		// We'll skip over any node that doesn't have any advertised
		// addresses. As we won't be able to reach them to actually
		// open any channels.
		if len(n.Addresses) == 0 {
			log.Tracef("Skipping unreachable node %x",
				n.PubKey.SerializeCompressed())
			return nil
		}

		node := dbNode{
			tx:   tx,
			node: n,
		}
		return cb(node)
	})
}

// addRandChannel creates a new channel two target nodes. This function is
// meant to aide in the generation of random graphs for use within test cases
// the exercise the autopilot package.
func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
	capacity btcutil.Amount) (*ChannelEdge, *ChannelEdge, error) {

	fetchNode := func(pub *btcec.PublicKey) (*channeldb.LightningNode, error) {
		if pub != nil {
			dbNode, err := d.db.FetchLightningNode(pub)
			switch {
			case err == channeldb.ErrGraphNodeNotFound:
				fallthrough
			case err == channeldb.ErrGraphNotFound:
				graphNode := &channeldb.LightningNode{
					PubKey:               pub,
					HaveNodeAnnouncement: true,
					Addresses: []net.Addr{
						&net.TCPAddr{
							IP: bytes.Repeat([]byte("a"), 16),
						},
					},
					Features: lnwire.NewFeatureVector(nil),
					AuthSig:  testSig,
				}
				if err := d.db.AddLightningNode(graphNode); err != nil {
					return nil, err
				}
			case err != nil:
				return nil, err
			}

			return dbNode, nil
		}

		nodeKey, err := randKey()
		if err != nil {
			return nil, err
		}
		dbNode := &channeldb.LightningNode{
			PubKey:               nodeKey,
			HaveNodeAnnouncement: true,
			Addresses: []net.Addr{
				&net.TCPAddr{
					IP: bytes.Repeat([]byte("a"), 16),
				},
			},
			Features: lnwire.NewFeatureVector(nil),
			AuthSig:  testSig,
		}
		if err := d.db.AddLightningNode(dbNode); err != nil {
			return nil, err
		}

		return dbNode, nil
	}

	vertex1, err := fetchNode(node1)
	if err != nil {
		return nil, nil, err
	}

	vertex2, err := fetchNode(node2)
	if err != nil {
		return nil, nil, err
	}

	var lnNode1, lnNode2 *btcec.PublicKey
	node1Bytes := vertex1.PubKey.SerializeCompressed()
	node2Bytes := vertex2.PubKey.SerializeCompressed()
	if bytes.Compare(node1Bytes, node2Bytes) == -1 {
		lnNode1 = vertex1.PubKey
		lnNode2 = vertex2.PubKey
	} else {
		lnNode1 = vertex2.PubKey
		lnNode2 = vertex1.PubKey
	}

	chanID := randChanID()
	edge := &channeldb.ChannelEdgeInfo{
		ChannelID:   chanID.ToUint64(),
		NodeKey1:    lnNode1,
		NodeKey2:    lnNode2,
		BitcoinKey1: vertex1.PubKey,
		BitcoinKey2: vertex2.PubKey,
		Capacity:    capacity,
	}
	if err := d.db.AddChannelEdge(edge); err != nil {
		return nil, nil, err
	}
	edgePolicy := &channeldb.ChannelEdgePolicy{
		Signature:                 testSig,
		ChannelID:                 chanID.ToUint64(),
		LastUpdate:                time.Now(),
		TimeLockDelta:             10,
		MinHTLC:                   1,
		FeeBaseMSat:               10,
		FeeProportionalMillionths: 10000,
		Flags: 0,
	}

	if err := d.db.UpdateEdgePolicy(edgePolicy); err != nil {
		return nil, nil, err
	}
	edgePolicy = &channeldb.ChannelEdgePolicy{
		Signature:                 testSig,
		ChannelID:                 chanID.ToUint64(),
		LastUpdate:                time.Now(),
		TimeLockDelta:             10,
		MinHTLC:                   1,
		FeeBaseMSat:               10,
		FeeProportionalMillionths: 10000,
		Flags: 1,
	}
	if err := d.db.UpdateEdgePolicy(edgePolicy); err != nil {
		return nil, nil, err
	}

	return &ChannelEdge{
			Channel: Channel{
				ChanID:   chanID,
				Capacity: capacity,
			},
			Peer: dbNode{
				node: vertex1,
			},
		},
		&ChannelEdge{
			Channel: Channel{
				ChanID:   chanID,
				Capacity: capacity,
			},
			Peer: dbNode{
				node: vertex2,
			},
		},
		nil
}

// memChannelGraph is a implementation of the autopilot.ChannelGraph backed by
// an in-memory graph.
type memChannelGraph struct {
	graph map[NodeID]memNode
}

// A compile time assertion to ensure memChannelGraph meets the
// autopilot.ChannelGraph interface.
var _ ChannelGraph = (*memChannelGraph)(nil)

// newMemChannelGraph creates a new blank in-memory channel graph
// implementation.
func newMemChannelGraph() *memChannelGraph {
	return &memChannelGraph{
		graph: make(map[NodeID]memNode),
	}
}

// ForEachNode is a higher-order function that should be called once for each
// connected node within the channel graph. If the passed callback returns an
// error, then execution should be terminated.
//
// NOTE: Part of the autopilot.ChannelGraph interface.
func (m memChannelGraph) ForEachNode(cb func(Node) error) error {
	for _, node := range m.graph {
		if err := cb(node); err != nil {
			return err
		}
	}

	return nil
}

// randChanID generates a new random channel ID.
func randChanID() lnwire.ShortChannelID {
	id := atomic.AddUint64(&chanIDCounter, 1)
	return lnwire.NewShortChanIDFromInt(id)
}

// randKey returns a random public key.
func randKey() (*btcec.PublicKey, error) {
	priv, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return nil, err
	}

	return priv.PubKey(), nil
}

// addRandChannel creates a new channel two target nodes. This function is
// meant to aide in the generation of random graphs for use within test cases
// the exercise the autopilot package.
func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
	capacity btcutil.Amount) (*ChannelEdge, *ChannelEdge, error) {

	var (
		vertex1, vertex2 memNode
		ok               bool
	)

	if node1 != nil {
		vertex1, ok = m.graph[NewNodeID(node1)]
		if !ok {
			vertex1 = memNode{
				pub: node1,
			}
		}
	} else {
		newPub, err := randKey()
		if err != nil {
			return nil, nil, err
		}
		vertex1 = memNode{
			pub: newPub,
		}
	}

	if node2 != nil {
		vertex2, ok = m.graph[NewNodeID(node2)]
		if !ok {
			vertex2 = memNode{
				pub: node2,
			}
		}
	} else {
		newPub, err := randKey()
		if err != nil {
			return nil, nil, err
		}
		vertex2 = memNode{
			pub: newPub,
		}
	}

	channel := Channel{
		ChanID:   randChanID(),
		Capacity: capacity,
	}

	edge1 := ChannelEdge{
		Channel: channel,
		Peer:    vertex2,
	}
	vertex1.chans = append(vertex1.chans, edge1)

	edge2 := ChannelEdge{
		Channel: channel,
		Peer:    vertex1,
	}
	vertex2.chans = append(vertex2.chans, edge2)

	m.graph[NewNodeID(vertex1.pub)] = vertex1
	m.graph[NewNodeID(vertex2.pub)] = vertex2

	return &edge1, &edge2, nil
}

// memNode is a purely in-memory implementation of the autopilot.Node
// interface.
type memNode struct {
	pub *btcec.PublicKey

	chans []ChannelEdge

	addrs []net.Addr
}

// A compile time assertion to ensure memNode meets the autopilot.Node
// interface.
var _ Node = (*memNode)(nil)

// PubKey is the identity public key of the node. This will be used to attempt
// to target a node for channel opening by the main autopilot agent.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) PubKey() *btcec.PublicKey {
	return m.pub
}

// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) Addrs() []net.Addr {
	return m.addrs
}

// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) ForEachChannel(cb func(ChannelEdge) error) error {
	for _, channel := range m.chans {
		if err := cb(channel); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>autopilot: remove spammy trace log<commit_after>package autopilot

import (
	"bytes"
	"math/big"
	"net"
	"sync/atomic"
	"time"

	"github.com/boltdb/bolt"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcutil"
)

var (
	testSig = &btcec.Signature{
		R: new(big.Int),
		S: new(big.Int),
	}
	_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
	_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)

	chanIDCounter uint64
)

// databaseChannelGraph wraps a channeldb.ChannelGraph instance with the
// necessary API to properly implement the autopilot.ChannelGraph interface.
//
// TODO(roasbeef): move inmpl to main package?
type databaseChannelGraph struct {
	db *channeldb.ChannelGraph
}

// A compile time assertion to ensure databaseChannelGraph meets the
// autopilot.ChannelGraph interface.
var _ ChannelGraph = (*databaseChannelGraph)(nil)

// ChannelGraphFromDatabase returns a instance of the autopilot.ChannelGraph
// backed by a live, open channeldb instance.
func ChannelGraphFromDatabase(db *channeldb.ChannelGraph) ChannelGraph {
	return &databaseChannelGraph{
		db: db,
	}
}

// type dbNode is a wrapper struct around a database transaction an
// channeldb.LightningNode. The wrapper method implement the autopilot.Node
// interface.
type dbNode struct {
	tx *bolt.Tx

	node *channeldb.LightningNode
}

// A compile time assertion to ensure dbNode meets the autopilot.Node
// interface.
var _ Node = (*dbNode)(nil)

// PubKey is the identity public key of the node. This will be used to attempt
// to target a node for channel opening by the main autopilot agent.
//
// NOTE: Part of the autopilot.Node interface.
func (d dbNode) PubKey() *btcec.PublicKey {
	return d.node.PubKey
}

// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (d dbNode) Addrs() []net.Addr {
	return d.node.Addresses
}

// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (d dbNode) ForEachChannel(cb func(ChannelEdge) error) error {
	return d.node.ForEachChannel(d.tx, func(tx *bolt.Tx,
		ei *channeldb.ChannelEdgeInfo, ep, _ *channeldb.ChannelEdgePolicy) error {

		edge := ChannelEdge{
			Channel: Channel{
				ChanID:    lnwire.NewShortChanIDFromInt(ep.ChannelID),
				Capacity:  ei.Capacity,
				FundedAmt: ei.Capacity,
				Node:      NewNodeID(ep.Node.PubKey),
			},
			Peer: dbNode{
				tx:   tx,
				node: ep.Node,
			},
		}

		return cb(edge)
	})
}

// ForEachNode is a higher-order function that should be called once for each
// connected node within the channel graph. If the passed callback returns an
// error, then execution should be terminated.
//
// NOTE: Part of the autopilot.ChannelGraph interface.
func (d *databaseChannelGraph) ForEachNode(cb func(Node) error) error {
	return d.db.ForEachNode(nil, func(tx *bolt.Tx, n *channeldb.LightningNode) error {

		// We'll skip over any node that doesn't have any advertised
		// addresses. As we won't be able to reach them to actually
		// open any channels.
		if len(n.Addresses) == 0 {
			return nil
		}

		node := dbNode{
			tx:   tx,
			node: n,
		}
		return cb(node)
	})
}

// addRandChannel creates a new channel two target nodes. This function is
// meant to aide in the generation of random graphs for use within test cases
// the exercise the autopilot package.
func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
	capacity btcutil.Amount) (*ChannelEdge, *ChannelEdge, error) {

	fetchNode := func(pub *btcec.PublicKey) (*channeldb.LightningNode, error) {
		if pub != nil {
			dbNode, err := d.db.FetchLightningNode(pub)
			switch {
			case err == channeldb.ErrGraphNodeNotFound:
				fallthrough
			case err == channeldb.ErrGraphNotFound:
				graphNode := &channeldb.LightningNode{
					PubKey:               pub,
					HaveNodeAnnouncement: true,
					Addresses: []net.Addr{
						&net.TCPAddr{
							IP: bytes.Repeat([]byte("a"), 16),
						},
					},
					Features: lnwire.NewFeatureVector(nil),
					AuthSig:  testSig,
				}
				if err := d.db.AddLightningNode(graphNode); err != nil {
					return nil, err
				}
			case err != nil:
				return nil, err
			}

			return dbNode, nil
		}

		nodeKey, err := randKey()
		if err != nil {
			return nil, err
		}
		dbNode := &channeldb.LightningNode{
			PubKey:               nodeKey,
			HaveNodeAnnouncement: true,
			Addresses: []net.Addr{
				&net.TCPAddr{
					IP: bytes.Repeat([]byte("a"), 16),
				},
			},
			Features: lnwire.NewFeatureVector(nil),
			AuthSig:  testSig,
		}
		if err := d.db.AddLightningNode(dbNode); err != nil {
			return nil, err
		}

		return dbNode, nil
	}

	vertex1, err := fetchNode(node1)
	if err != nil {
		return nil, nil, err
	}

	vertex2, err := fetchNode(node2)
	if err != nil {
		return nil, nil, err
	}

	var lnNode1, lnNode2 *btcec.PublicKey
	node1Bytes := vertex1.PubKey.SerializeCompressed()
	node2Bytes := vertex2.PubKey.SerializeCompressed()
	if bytes.Compare(node1Bytes, node2Bytes) == -1 {
		lnNode1 = vertex1.PubKey
		lnNode2 = vertex2.PubKey
	} else {
		lnNode1 = vertex2.PubKey
		lnNode2 = vertex1.PubKey
	}

	chanID := randChanID()
	edge := &channeldb.ChannelEdgeInfo{
		ChannelID:   chanID.ToUint64(),
		NodeKey1:    lnNode1,
		NodeKey2:    lnNode2,
		BitcoinKey1: vertex1.PubKey,
		BitcoinKey2: vertex2.PubKey,
		Capacity:    capacity,
	}
	if err := d.db.AddChannelEdge(edge); err != nil {
		return nil, nil, err
	}
	edgePolicy := &channeldb.ChannelEdgePolicy{
		Signature:                 testSig,
		ChannelID:                 chanID.ToUint64(),
		LastUpdate:                time.Now(),
		TimeLockDelta:             10,
		MinHTLC:                   1,
		FeeBaseMSat:               10,
		FeeProportionalMillionths: 10000,
		Flags: 0,
	}

	if err := d.db.UpdateEdgePolicy(edgePolicy); err != nil {
		return nil, nil, err
	}
	edgePolicy = &channeldb.ChannelEdgePolicy{
		Signature:                 testSig,
		ChannelID:                 chanID.ToUint64(),
		LastUpdate:                time.Now(),
		TimeLockDelta:             10,
		MinHTLC:                   1,
		FeeBaseMSat:               10,
		FeeProportionalMillionths: 10000,
		Flags: 1,
	}
	if err := d.db.UpdateEdgePolicy(edgePolicy); err != nil {
		return nil, nil, err
	}

	return &ChannelEdge{
			Channel: Channel{
				ChanID:   chanID,
				Capacity: capacity,
			},
			Peer: dbNode{
				node: vertex1,
			},
		},
		&ChannelEdge{
			Channel: Channel{
				ChanID:   chanID,
				Capacity: capacity,
			},
			Peer: dbNode{
				node: vertex2,
			},
		},
		nil
}

// memChannelGraph is a implementation of the autopilot.ChannelGraph backed by
// an in-memory graph.
type memChannelGraph struct {
	graph map[NodeID]memNode
}

// A compile time assertion to ensure memChannelGraph meets the
// autopilot.ChannelGraph interface.
var _ ChannelGraph = (*memChannelGraph)(nil)

// newMemChannelGraph creates a new blank in-memory channel graph
// implementation.
func newMemChannelGraph() *memChannelGraph {
	return &memChannelGraph{
		graph: make(map[NodeID]memNode),
	}
}

// ForEachNode is a higher-order function that should be called once for each
// connected node within the channel graph. If the passed callback returns an
// error, then execution should be terminated.
//
// NOTE: Part of the autopilot.ChannelGraph interface.
func (m memChannelGraph) ForEachNode(cb func(Node) error) error {
	for _, node := range m.graph {
		if err := cb(node); err != nil {
			return err
		}
	}

	return nil
}

// randChanID generates a new random channel ID.
func randChanID() lnwire.ShortChannelID {
	id := atomic.AddUint64(&chanIDCounter, 1)
	return lnwire.NewShortChanIDFromInt(id)
}

// randKey returns a random public key.
func randKey() (*btcec.PublicKey, error) {
	priv, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return nil, err
	}

	return priv.PubKey(), nil
}

// addRandChannel creates a new channel two target nodes. This function is
// meant to aide in the generation of random graphs for use within test cases
// the exercise the autopilot package.
func (m *memChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
	capacity btcutil.Amount) (*ChannelEdge, *ChannelEdge, error) {

	var (
		vertex1, vertex2 memNode
		ok               bool
	)

	if node1 != nil {
		vertex1, ok = m.graph[NewNodeID(node1)]
		if !ok {
			vertex1 = memNode{
				pub: node1,
			}
		}
	} else {
		newPub, err := randKey()
		if err != nil {
			return nil, nil, err
		}
		vertex1 = memNode{
			pub: newPub,
		}
	}

	if node2 != nil {
		vertex2, ok = m.graph[NewNodeID(node2)]
		if !ok {
			vertex2 = memNode{
				pub: node2,
			}
		}
	} else {
		newPub, err := randKey()
		if err != nil {
			return nil, nil, err
		}
		vertex2 = memNode{
			pub: newPub,
		}
	}

	channel := Channel{
		ChanID:   randChanID(),
		Capacity: capacity,
	}

	edge1 := ChannelEdge{
		Channel: channel,
		Peer:    vertex2,
	}
	vertex1.chans = append(vertex1.chans, edge1)

	edge2 := ChannelEdge{
		Channel: channel,
		Peer:    vertex1,
	}
	vertex2.chans = append(vertex2.chans, edge2)

	m.graph[NewNodeID(vertex1.pub)] = vertex1
	m.graph[NewNodeID(vertex2.pub)] = vertex2

	return &edge1, &edge2, nil
}

// memNode is a purely in-memory implementation of the autopilot.Node
// interface.
type memNode struct {
	pub *btcec.PublicKey

	chans []ChannelEdge

	addrs []net.Addr
}

// A compile time assertion to ensure memNode meets the autopilot.Node
// interface.
var _ Node = (*memNode)(nil)

// PubKey is the identity public key of the node. This will be used to attempt
// to target a node for channel opening by the main autopilot agent.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) PubKey() *btcec.PublicKey {
	return m.pub
}

// Addrs returns a slice of publicly reachable public TCP addresses that the
// peer is known to be listening on.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) Addrs() []net.Addr {
	return m.addrs
}

// ForEachChannel is a higher-order function that will be used to iterate
// through all edges emanating from/to the target node. For each active
// channel, this function should be called with the populated ChannelEdge that
// describes the active channel.
//
// NOTE: Part of the autopilot.Node interface.
func (m memNode) ForEachChannel(cb func(ChannelEdge) error) error {
	for _, channel := range m.chans {
		if err := cb(channel); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package stringutil

import "sync"

type StringDeduplicator struct {
	lock       bool
	mutex      sync.Mutex
	mapping    map[string]string
	statistics StringDuplicationStatistics
}

type StringDuplicationStatistics struct {
	DuplicateBytes   uint64
	DuplicateStrings uint64
	UniqueBytes      uint64
	UniqueStrings    uint64
}

func NewStringDeduplicator(lock bool) *StringDeduplicator {
	return &StringDeduplicator{lock: lock, mapping: make(map[string]string)}
}

func (d *StringDeduplicator) Clear() {
	d.clear()
}

func (d *StringDeduplicator) DeDuplicate(str string) string {
	return d.deDuplicate(str)
}

func (d *StringDeduplicator) GetStatistics() StringDuplicationStatistics {
	return d.getStatistics()
}
<commit_msg>Add basic documentation for lib/stringutil package.<commit_after>package stringutil

import "sync"

type StringDeduplicator struct {
	lock       bool
	mutex      sync.Mutex
	mapping    map[string]string
	statistics StringDuplicationStatistics
}

type StringDuplicationStatistics struct {
	DuplicateBytes   uint64
	DuplicateStrings uint64
	UniqueBytes      uint64
	UniqueStrings    uint64
}

// NewStringDeduplicator will create a StringDeduplicator which may be used to
// eliminate duplicate string contents. It maintains an internal map of unique
// strings. If lock is true then each method call will take an exclusive lock.
func NewStringDeduplicator(lock bool) *StringDeduplicator {
	return &StringDeduplicator{lock: lock, mapping: make(map[string]string)}
}

// Clear will clear the internal map and statistics.
func (d *StringDeduplicator) Clear() {
	d.clear()
}

// DeDuplicate will return a string which has the same contents as str. This
// method should be called for every string in the application.
func (d *StringDeduplicator) DeDuplicate(str string) string {
	return d.deDuplicate(str)
}

// GetStatistics will return de-duplication statistics.
func (d *StringDeduplicator) GetStatistics() StringDuplicationStatistics {
	return d.getStatistics()
}
<|endoftext|>


<commit_before>package citibank

import (
	"net/http"
	"strconv"

	"github.com/PMoneda/flow"
	"github.com/mundipagg/boleto-api/config"
	"github.com/mundipagg/boleto-api/log"
	"github.com/mundipagg/boleto-api/metrics"
	"github.com/mundipagg/boleto-api/models"
	"github.com/mundipagg/boleto-api/tmpl"
	"github.com/mundipagg/boleto-api/util"
	"github.com/mundipagg/boleto-api/validations"
)

type bankCiti struct {
	validate  *models.Validator
	log       *log.Log
	transport *http.Transport
}

func New() bankCiti {
	b := bankCiti{
		validate: models.NewValidator(),
		log:      log.CreateLog(),
	}
	b.validate.Push(validations.ValidateAmount)
	b.validate.Push(validations.ValidateExpireDate)
	b.validate.Push(validations.ValidateBuyerDocumentNumber)
	b.validate.Push(validations.ValidateRecipientDocumentNumber)
	b.validate.Push(citiValidateAgency)
	b.validate.Push(citiValidateAccount)
	b.validate.Push(citiValidateWallet)
	transp, err := util.BuildTLSTransport(config.Get().CertBoletoPathCrt, config.Get().CertBoletoPathKey, config.Get().CertBoletoPathCa)
	if err != nil {
		//TODO
	}
	b.transport = transp
	return b
}

//Log retorna a referencia do log
func (b bankCiti) Log() *log.Log {
	return b.log
}

func (b bankCiti) RegisterBoleto(boleto *models.BoletoRequest) (models.BoletoResponse, error) {
	timing := metrics.GetTimingMetrics()
	boleto.Title.OurNumber = calculateOurNumber(boleto)
	r := flow.NewFlow()
	serviceURL := config.Get().URLCiti
	from := getResponseCiti()
	to := getAPIResponseCiti()
	bod := r.From("message://?source=inline", boleto, getRequestCiti(), tmpl.GetFuncMaps())
	bod.To("logseq://?type=request&url="+serviceURL, b.log)
	//TODO: change for tls flow connector (waiting for santander)
	var responseCiti string
	var status int
	var err error
	duration := util.Duration(func() {
		responseCiti, status, err = b.sendRequest(bod.GetBody().(string))
	})
	if err != nil {
		return models.BoletoResponse{}, err
	}
	timing.Push("citibank-register-boleto-online", duration.Seconds())
	bod.To("set://?prop=header", map[string]string{"status": strconv.Itoa(status)})
	bod.To("set://?prop=body", responseCiti)
	bod.To("logseq://?type=response&url="+serviceURL, b.log)
	ch := bod.Choice()
	ch.When(flow.Header("status").IsEqualTo("200"))
	ch.To("transform://?format=xml", from, to, tmpl.GetFuncMaps())
	ch.Otherwise()
	ch.To("logseq://?type=response&url="+serviceURL, b.log).To("apierro://")

	switch t := bod.GetBody().(type) {
	case string:
		response := util.ParseJSON(t, new(models.BoletoResponse)).(*models.BoletoResponse)
		return *response, nil
	case models.BoletoResponse:
		return t, nil
	}
	return models.BoletoResponse{}, models.NewInternalServerError("MP500", "Internal error")
}

func (b bankCiti) ProcessBoleto(boleto *models.BoletoRequest) (models.BoletoResponse, error) {
	errs := b.ValidateBoleto(boleto)
	if len(errs) > 0 {
		return models.BoletoResponse{Errors: errs}, nil
	}
	return b.RegisterBoleto(boleto)
}

func (b bankCiti) ValidateBoleto(boleto *models.BoletoRequest) models.Errors {
	return models.Errors(b.validate.Assert(boleto))
}

func (b bankCiti) sendRequest(body string) (string, int, error) {
	serviceURL := config.Get().URLCiti
	if config.Get().MockMode {
		return util.Post(serviceURL, body, map[string]string{"Soapaction": "RegisterBoleto"})
	} else {
		return util.PostTLS(serviceURL, body, map[string]string{"Soapaction": "RegisterBoleto"}, b.transport)
	}
}

//GetBankNumber retorna o codigo do banco
func (b bankCiti) GetBankNumber() models.BankNumber {
	return models.Citibank
}

func calculateOurNumber(boleto *models.BoletoRequest) uint {
	ourNumberWithDigit := strconv.Itoa(int(boleto.Title.OurNumber)) + util.OurNumberDv(strconv.Itoa(int(boleto.Title.OurNumber)), util.MOD11)
	value, _ := strconv.Atoi(ourNumberWithDigit)
	return uint(value)
}

func (b bankCiti) GetBankNameIntegration() string {
	return "Citibank"
}
<commit_msg>:sparkles: Implementa timeout Citibank<commit_after>package citibank

import (
	"net/http"
	"strconv"

	"github.com/PMoneda/flow"
	"github.com/mundipagg/boleto-api/config"
	"github.com/mundipagg/boleto-api/log"
	"github.com/mundipagg/boleto-api/metrics"
	"github.com/mundipagg/boleto-api/models"
	"github.com/mundipagg/boleto-api/tmpl"
	"github.com/mundipagg/boleto-api/util"
	"github.com/mundipagg/boleto-api/validations"
)

type bankCiti struct {
	validate  *models.Validator
	log       *log.Log
	transport *http.Transport
}

func New() bankCiti {
	b := bankCiti{
		validate: models.NewValidator(),
		log:      log.CreateLog(),
	}
	b.validate.Push(validations.ValidateAmount)
	b.validate.Push(validations.ValidateExpireDate)
	b.validate.Push(validations.ValidateBuyerDocumentNumber)
	b.validate.Push(validations.ValidateRecipientDocumentNumber)
	b.validate.Push(citiValidateAgency)
	b.validate.Push(citiValidateAccount)
	b.validate.Push(citiValidateWallet)
	transp, err := util.BuildTLSTransport(config.Get().CertBoletoPathCrt, config.Get().CertBoletoPathKey, config.Get().CertBoletoPathCa)
	if err != nil {
		//TODO
	}
	b.transport = transp
	return b
}

//Log retorna a referencia do log
func (b bankCiti) Log() *log.Log {
	return b.log
}

func (b bankCiti) RegisterBoleto(boleto *models.BoletoRequest) (models.BoletoResponse, error) {
	timing := metrics.GetTimingMetrics()
	boleto.Title.OurNumber = calculateOurNumber(boleto)
	r := flow.NewFlow()
	serviceURL := config.Get().URLCiti
	from := getResponseCiti()
	to := getAPIResponseCiti()
	bod := r.From("message://?source=inline", boleto, getRequestCiti(), tmpl.GetFuncMaps())
	bod.To("logseq://?type=request&url="+serviceURL, b.log)
	//TODO: change for tls flow connector (waiting for santander)
	var responseCiti string
	var status int
	var err error
	duration := util.Duration(func() {
		responseCiti, status, err = b.sendRequest(bod.GetBody().(string))
	})
	if err != nil {
		return models.BoletoResponse{}, err
	}
	timing.Push("citibank-register-boleto-online", duration.Seconds())
	bod.To("set://?prop=header", map[string]string{"status": strconv.Itoa(status)})
	bod.To("set://?prop=body", responseCiti)
	bod.To("logseq://?type=response&url="+serviceURL, b.log)
	ch := bod.Choice()
	ch.When(flow.Header("status").IsEqualTo("200"))
	ch.To("transform://?format=xml", from, to, tmpl.GetFuncMaps())
	ch.Otherwise()
	ch.To("logseq://?type=response&url="+serviceURL, b.log).To("apierro://")

	switch t := bod.GetBody().(type) {
	case string:
		response := util.ParseJSON(t, new(models.BoletoResponse)).(*models.BoletoResponse)
		return *response, nil
	case models.BoletoResponse:
		return t, nil
	}
	return models.BoletoResponse{}, models.NewInternalServerError("MP500", "Internal error")
}

func (b bankCiti) ProcessBoleto(boleto *models.BoletoRequest) (models.BoletoResponse, error) {
	errs := b.ValidateBoleto(boleto)
	if len(errs) > 0 {
		return models.BoletoResponse{Errors: errs}, nil
	}
	return b.RegisterBoleto(boleto)
}

func (b bankCiti) ValidateBoleto(boleto *models.BoletoRequest) models.Errors {
	return models.Errors(b.validate.Assert(boleto))
}

func (b bankCiti) sendRequest(body string) (string, int, error) {
	serviceURL := config.Get().URLCiti
	if config.Get().MockMode {
		return util.Post(serviceURL, body, config.Get().TimeoutDefault, map[string]string{"Soapaction": "RegisterBoleto"})
	} else {
		return util.PostTLS(serviceURL, body, config.Get().TimeoutDefault, map[string]string{"Soapaction": "RegisterBoleto"}, b.transport)
	}
}

//GetBankNumber retorna o codigo do banco
func (b bankCiti) GetBankNumber() models.BankNumber {
	return models.Citibank
}

func calculateOurNumber(boleto *models.BoletoRequest) uint {
	ourNumberWithDigit := strconv.Itoa(int(boleto.Title.OurNumber)) + util.OurNumberDv(strconv.Itoa(int(boleto.Title.OurNumber)), util.MOD11)
	value, _ := strconv.Atoi(ourNumberWithDigit)
	return uint(value)
}

func (b bankCiti) GetBankNameIntegration() string {
	return "Citibank"
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package sync

import (
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/syncthing/syncthing/lib/logger"
)

const (
	logThreshold = 100 * time.Millisecond
	shortWait    = 5 * time.Millisecond
	longWait     = 125 * time.Millisecond
)

func TestTypes(t *testing.T) {
	debug = false
	l.SetDebug("sync", false)

	if _, ok := NewMutex().(*sync.Mutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewRWMutex().(*sync.RWMutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewWaitGroup().(*sync.WaitGroup); !ok {
		t.Error("Wrong type")
	}

	debug = true
	l.SetDebug("sync", true)

	if _, ok := NewMutex().(*loggedMutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewRWMutex().(*loggedRWMutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewWaitGroup().(*loggedWaitGroup); !ok {
		t.Error("Wrong type")
	}

	debug = false
	l.SetDebug("sync", false)
}

func TestMutex(t *testing.T) {
	debug = true
	l.SetDebug("sync", true)
	threshold = logThreshold

	msgmut := sync.Mutex{}
	var messages []string

	l.AddHandler(logger.LevelDebug, func(_ logger.LogLevel, message string) {
		msgmut.Lock()
		messages = append(messages, message)
		msgmut.Unlock()
	})

	mut := NewMutex()
	mut.Lock()
	time.Sleep(shortWait)
	mut.Unlock()

	if len(messages) > 0 {
		t.Errorf("Unexpected message count")
	}

	mut.Lock()
	time.Sleep(longWait)
	mut.Unlock()

	if len(messages) != 1 {
		t.Errorf("Unexpected message count")
	}

	debug = false
	l.SetDebug("sync", false)
}

func TestRWMutex(t *testing.T) {
	debug = true
	l.SetDebug("sync", true)
	threshold = logThreshold

	msgmut := sync.Mutex{}
	var messages []string

	l.AddHandler(logger.LevelDebug, func(_ logger.LogLevel, message string) {
		msgmut.Lock()
		messages = append(messages, message)
		msgmut.Unlock()
	})

	mut := NewRWMutex()
	mut.Lock()
	time.Sleep(shortWait)
	mut.Unlock()

	if len(messages) > 0 {
		t.Errorf("Unexpected message count")
	}

	mut.Lock()
	time.Sleep(longWait)
	mut.Unlock()

	if len(messages) != 1 {
		t.Errorf("Unexpected message count")
	}

	// Testing rlocker logging
	mut.RLock()
	go func() {
		time.Sleep(longWait)
		mut.RUnlock()
	}()

	mut.Lock()
	mut.Unlock()

	if len(messages) != 2 {
		t.Errorf("Unexpected message count")
	}
	if !strings.Contains(messages[1], "RUnlockers while locking: sync") || !strings.Contains(messages[1], "sync_test.go:") {
		t.Error("Unexpected message")
	}

	// Testing multiple rlockers
	mut.RLock()
	mut.RLock()
	mut.RLock()
	mut.RUnlock()
	mut.RUnlock()
	mut.RUnlock()

	debug = false
	l.SetDebug("sync", false)
}

func TestWaitGroup(t *testing.T) {
	debug = true
	l.SetDebug("sync", true)
	threshold = logThreshold

	msgmut := sync.Mutex{}
	var messages []string

	l.AddHandler(logger.LevelDebug, func(_ logger.LogLevel, message string) {
		msgmut.Lock()
		messages = append(messages, message)
		msgmut.Unlock()
	})

	wg := NewWaitGroup()
	wg.Add(1)
	go func() {
		time.Sleep(shortWait)
		wg.Done()
	}()
	wg.Wait()

	if len(messages) > 0 {
		t.Errorf("Unexpected message count")
	}

	wg = NewWaitGroup()
	wg.Add(1)
	go func() {
		time.Sleep(longWait)
		wg.Done()
	}()
	wg.Wait()

	if len(messages) != 1 {
		t.Errorf("Unexpected message count")
	}

	debug = false
	l.SetDebug("sync", false)
}
<commit_msg>lib/sync: Skip the timing tests if the host timer is flaky<commit_after>// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package sync

import (
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/syncthing/syncthing/lib/logger"
)

const (
	logThreshold = 100 * time.Millisecond
	shortWait    = 5 * time.Millisecond
	longWait     = 125 * time.Millisecond
)

var skipTimingTests = false

func init() {
	// Check a few times that a short sleep does not in fact overrun the log
	// threshold. If it does, the timer accuracy is crap or the host is
	// overloaded and we can't reliably run the tests in here. In the normal
	// case this takes just 25*5 = 125 ms.
	for i := 0; i < 25; i++ {
		t0 := time.Now()
		time.Sleep(shortWait)
		if time.Since(t0) > logThreshold {
			skipTimingTests = true
			return
		}
	}
}

func TestTypes(t *testing.T) {
	debug = false
	l.SetDebug("sync", false)

	if _, ok := NewMutex().(*sync.Mutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewRWMutex().(*sync.RWMutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewWaitGroup().(*sync.WaitGroup); !ok {
		t.Error("Wrong type")
	}

	debug = true
	l.SetDebug("sync", true)

	if _, ok := NewMutex().(*loggedMutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewRWMutex().(*loggedRWMutex); !ok {
		t.Error("Wrong type")
	}

	if _, ok := NewWaitGroup().(*loggedWaitGroup); !ok {
		t.Error("Wrong type")
	}

	debug = false
	l.SetDebug("sync", false)
}

func TestMutex(t *testing.T) {
	if skipTimingTests {
		t.Skip("insufficient timer accuracy")
		return
	}

	debug = true
	l.SetDebug("sync", true)
	threshold = logThreshold

	msgmut := sync.Mutex{}
	var messages []string

	l.AddHandler(logger.LevelDebug, func(_ logger.LogLevel, message string) {
		msgmut.Lock()
		messages = append(messages, message)
		msgmut.Unlock()
	})

	mut := NewMutex()
	mut.Lock()
	time.Sleep(shortWait)
	mut.Unlock()

	if len(messages) > 0 {
		t.Errorf("Unexpected message count")
	}

	mut.Lock()
	time.Sleep(longWait)
	mut.Unlock()

	if len(messages) != 1 {
		t.Errorf("Unexpected message count")
	}

	debug = false
	l.SetDebug("sync", false)
}

func TestRWMutex(t *testing.T) {
	if skipTimingTests {
		t.Skip("insufficient timer accuracy")
		return
	}

	debug = true
	l.SetDebug("sync", true)
	threshold = logThreshold

	msgmut := sync.Mutex{}
	var messages []string

	l.AddHandler(logger.LevelDebug, func(_ logger.LogLevel, message string) {
		msgmut.Lock()
		messages = append(messages, message)
		msgmut.Unlock()
	})

	mut := NewRWMutex()
	mut.Lock()
	time.Sleep(shortWait)
	mut.Unlock()

	if len(messages) > 0 {
		t.Errorf("Unexpected message count")
	}

	mut.Lock()
	time.Sleep(longWait)
	mut.Unlock()

	if len(messages) != 1 {
		t.Errorf("Unexpected message count")
	}

	// Testing rlocker logging
	mut.RLock()
	go func() {
		time.Sleep(longWait)
		mut.RUnlock()
	}()

	mut.Lock()
	mut.Unlock()

	if len(messages) != 2 {
		t.Errorf("Unexpected message count")
	}
	if !strings.Contains(messages[1], "RUnlockers while locking: sync") || !strings.Contains(messages[1], "sync_test.go:") {
		t.Error("Unexpected message")
	}

	// Testing multiple rlockers
	mut.RLock()
	mut.RLock()
	mut.RLock()
	mut.RUnlock()
	mut.RUnlock()
	mut.RUnlock()

	debug = false
	l.SetDebug("sync", false)
}

func TestWaitGroup(t *testing.T) {
	if skipTimingTests {
		t.Skip("insufficient timer accuracy")
		return
	}

	debug = true
	l.SetDebug("sync", true)
	threshold = logThreshold

	msgmut := sync.Mutex{}
	var messages []string

	l.AddHandler(logger.LevelDebug, func(_ logger.LogLevel, message string) {
		msgmut.Lock()
		messages = append(messages, message)
		msgmut.Unlock()
	})

	wg := NewWaitGroup()
	wg.Add(1)
	go func() {
		time.Sleep(shortWait)
		wg.Done()
	}()
	wg.Wait()

	if len(messages) > 0 {
		t.Errorf("Unexpected message count")
	}

	wg = NewWaitGroup()
	wg.Add(1)
	go func() {
		time.Sleep(longWait)
		wg.Done()
	}()
	wg.Wait()

	if len(messages) != 1 {
		t.Errorf("Unexpected message count")
	}

	debug = false
	l.SetDebug("sync", false)
}
<|endoftext|>