<commit_before>// +build linux

package systemd

import (
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	systemdDbus "github.com/coreos/go-systemd/dbus"
	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type UnifiedManager struct {
	mu      sync.Mutex
	Cgroups *configs.Cgroup
	Paths   map[string]string
}

func (m *UnifiedManager) Apply(pid int) error {
	var (
		c          = m.Cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemdDbus.Property
	)

	if c.Paths != nil {
		paths := make(map[string]string)
		for name, path := range c.Paths {
			_, err := getSubsystemPath(m.Cgroups, name)
			if err != nil {
				// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
				if cgroups.IsNotFound(err) {
					continue
				}
				return err
			}
			paths[name] = path
		}
		m.Paths = paths
		return cgroups.EnterPid(m.Paths, pid)
	}

	if c.Parent != "" {
		slice = c.Parent
	}

	properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))

	// if we create a slice, the parent is defined via a Wants=
	if strings.HasSuffix(unitName, ".slice") {
		properties = append(properties, systemdDbus.PropWants(slice))
	} else {
		// otherwise, we use Slice=
		properties = append(properties, systemdDbus.PropSlice(slice))
	}

	// only add pid if its valid, -1 is used w/ general slice creation.
	if pid != -1 {
		properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
	}

	// Check if we can delegate. This is only supported on systemd versions 218 and above.
	if !strings.HasSuffix(unitName, ".slice") {
		// Assume scopes always support delegation.
		properties = append(properties, newProp("Delegate", true))
	}

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("IOAccounting", true))

	// Assume DefaultDependencies= will always work (the check for it was previously broken.)
	properties = append(properties,
		newProp("DefaultDependencies", false))

	if c.Resources.Memory != 0 {
		properties = append(properties,
			newProp("MemoryMax", uint64(c.Resources.Memory)))
	}

	if c.Resources.CpuWeight != 0 {
		properties = append(properties,
			newProp("CPUWeight", c.Resources.CpuWeight))
	}

	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
		// corresponds to USEC_INFINITY in systemd
		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
		// always setting a property value ensures we can apply a quota and remove it later
		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
		if c.Resources.CpuQuota > 0 {
			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
			// (integer percentage of CPU) internally.  This means that if a fractional percent of
			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
			if cpuQuotaPerSecUSec%10000 != 0 {
				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
			}
		}
		properties = append(properties,
			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
	}

	if c.Resources.PidsLimit > 0 {
		properties = append(properties,
			newProp("TasksAccounting", true),
			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
	}

	properties = append(properties, c.SystemdProps...)

	// We have to set kernel memory here, as we can't change it once
	// processes have been attached to the cgroup.
	if c.Resources.KernelMemory != 0 {
		if err := setKernelMemory(c); err != nil {
			return err
		}
	}

	statusChan := make(chan string, 1)
	if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
		select {
		case <-statusChan:
		case <-time.After(time.Second):
			logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", unitName)
		}
	} else if !isUnitExists(err) {
		return err
	}

	if err := joinCgroupsV2(c, pid); err != nil {
		return err
	}

	path, err := getSubsystemPath(m.Cgroups, "")
	if err != nil {
		return err
	}
	m.Paths = map[string]string{
		"pids":    path,
		"memory":  path,
		"io":      path,
		"cpu":     path,
		"devices": path,
		"cpuset":  path,
		"freezer": path,
	}
	return nil
}

func (m *UnifiedManager) Destroy() error {
	if m.Cgroups.Paths != nil {
		return nil
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
	if err := cgroups.RemovePaths(m.Paths); err != nil {
		return err
	}
	m.Paths = make(map[string]string)
	return nil
}

func (m *UnifiedManager) GetPaths() map[string]string {
	m.mu.Lock()
	paths := m.Paths
	m.mu.Unlock()
	return paths
}
func (m *UnifiedManager) GetUnifiedPath() (string, error) {
	unifiedPath := ""
	m.mu.Lock()
	defer m.mu.Unlock()
	for k, v := range m.Paths {
		if unifiedPath == "" {
			unifiedPath = v
		} else if v != unifiedPath {
			return unifiedPath,
				errors.Errorf("expected %q path to be unified path %q, got %q", k, unifiedPath, v)
		}
	}
	if unifiedPath == "" {
		// FIXME: unified path could be detected even when no controller is available
		return unifiedPath, errors.New("cannot detect unified path")
	}
	return unifiedPath, nil
}
func createCgroupsv2Path(path string) (Err error) {
	content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
	if err != nil {
		return err
	}
	if !filepath.HasPrefix(path, "/sys/fs/cgroup") {
		return fmt.Errorf("invalid cgroup path %s", path)
	}

	res := ""
	for i, c := range strings.Split(strings.TrimSpace(string(content)), " ") {
		if i == 0 {
			res = fmt.Sprintf("+%s", c)
		} else {
			res = res + fmt.Sprintf(" +%s", c)
		}
	}
	resByte := []byte(res)

	current := "/sys/fs"
	elements := strings.Split(path, "/")
	for i, e := range elements[3:] {
		current = filepath.Join(current, e)
		if i > 0 {
			if err := os.Mkdir(current, 0755); err != nil {
				if !os.IsExist(err) {
					return err
				}
			} else {
				// If the directory was created, be sure it is not left around on errors.
				defer func() {
					if Err != nil {
						os.Remove(current)
					}
				}()
			}
		}
		if i < len(elements[3:])-1 {
			if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), resByte, 0755); err != nil {
				return err
			}
		}
	}
	return nil
}

func joinCgroupsV2(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "memory")
	if err != nil {
		return err
	}
	return createCgroupsv2Path(path)
}

func (m *UnifiedManager) fsManager() (cgroups.Manager, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return fs2.NewManager(m.Cgroups, path, false)
}

func (m *UnifiedManager) Freeze(state configs.FreezerState) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Freeze(state)
}

func (m *UnifiedManager) GetPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetPids(path)
}

func (m *UnifiedManager) GetAllPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetAllPids(path)
}

func (m *UnifiedManager) GetStats() (*cgroups.Stats, error) {
	fsMgr, err := m.fsManager()
	if err != nil {
		return nil, err
	}
	return fsMgr.GetStats()
}

func (m *UnifiedManager) Set(container *configs.Config) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Set(container)
}

func (m *UnifiedManager) GetCgroups() (*configs.Cgroup, error) {
	return m.Cgroups, nil
}
<commit_msg>libcontainer: simplify createCgroupsv2Path<commit_after>// +build linux

package systemd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	systemdDbus "github.com/coreos/go-systemd/dbus"
	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type UnifiedManager struct {
	mu      sync.Mutex
	Cgroups *configs.Cgroup
	Paths   map[string]string
}

func (m *UnifiedManager) Apply(pid int) error {
	var (
		c          = m.Cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemdDbus.Property
	)

	if c.Paths != nil {
		paths := make(map[string]string)
		for name, path := range c.Paths {
			_, err := getSubsystemPath(m.Cgroups, name)
			if err != nil {
				// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
				if cgroups.IsNotFound(err) {
					continue
				}
				return err
			}
			paths[name] = path
		}
		m.Paths = paths
		return cgroups.EnterPid(m.Paths, pid)
	}

	if c.Parent != "" {
		slice = c.Parent
	}

	properties = append(properties, systemdDbus.PropDescription("libcontainer container "+c.Name))

	// if we create a slice, the parent is defined via a Wants=
	if strings.HasSuffix(unitName, ".slice") {
		properties = append(properties, systemdDbus.PropWants(slice))
	} else {
		// otherwise, we use Slice=
		properties = append(properties, systemdDbus.PropSlice(slice))
	}

	// only add pid if its valid, -1 is used w/ general slice creation.
	if pid != -1 {
		properties = append(properties, newProp("PIDs", []uint32{uint32(pid)}))
	}

	// Check if we can delegate. This is only supported on systemd versions 218 and above.
	if !strings.HasSuffix(unitName, ".slice") {
		// Assume scopes always support delegation.
		properties = append(properties, newProp("Delegate", true))
	}

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("IOAccounting", true))

	// Assume DefaultDependencies= will always work (the check for it was previously broken.)
	properties = append(properties,
		newProp("DefaultDependencies", false))

	if c.Resources.Memory != 0 {
		properties = append(properties,
			newProp("MemoryMax", uint64(c.Resources.Memory)))
	}

	if c.Resources.CpuWeight != 0 {
		properties = append(properties,
			newProp("CPUWeight", c.Resources.CpuWeight))
	}

	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
		// corresponds to USEC_INFINITY in systemd
		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
		// always setting a property value ensures we can apply a quota and remove it later
		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
		if c.Resources.CpuQuota > 0 {
			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
			// (integer percentage of CPU) internally.  This means that if a fractional percent of
			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
			if cpuQuotaPerSecUSec%10000 != 0 {
				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
			}
		}
		properties = append(properties,
			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
	}

	if c.Resources.PidsLimit > 0 {
		properties = append(properties,
			newProp("TasksAccounting", true),
			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
	}

	properties = append(properties, c.SystemdProps...)

	// We have to set kernel memory here, as we can't change it once
	// processes have been attached to the cgroup.
	if c.Resources.KernelMemory != 0 {
		if err := setKernelMemory(c); err != nil {
			return err
		}
	}

	statusChan := make(chan string, 1)
	if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
		select {
		case <-statusChan:
		case <-time.After(time.Second):
			logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", unitName)
		}
	} else if !isUnitExists(err) {
		return err
	}

	if err := joinCgroupsV2(c, pid); err != nil {
		return err
	}

	path, err := getSubsystemPath(m.Cgroups, "")
	if err != nil {
		return err
	}
	m.Paths = map[string]string{
		"pids":    path,
		"memory":  path,
		"io":      path,
		"cpu":     path,
		"devices": path,
		"cpuset":  path,
		"freezer": path,
	}
	return nil
}

func (m *UnifiedManager) Destroy() error {
	if m.Cgroups.Paths != nil {
		return nil
	}
	m.mu.Lock()
	defer m.mu.Unlock()
	theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
	if err := cgroups.RemovePaths(m.Paths); err != nil {
		return err
	}
	m.Paths = make(map[string]string)
	return nil
}

func (m *UnifiedManager) GetPaths() map[string]string {
	m.mu.Lock()
	paths := m.Paths
	m.mu.Unlock()
	return paths
}
func (m *UnifiedManager) GetUnifiedPath() (string, error) {
	unifiedPath := ""
	m.mu.Lock()
	defer m.mu.Unlock()
	for k, v := range m.Paths {
		if unifiedPath == "" {
			unifiedPath = v
		} else if v != unifiedPath {
			return unifiedPath,
				errors.Errorf("expected %q path to be unified path %q, got %q", k, unifiedPath, v)
		}
	}
	if unifiedPath == "" {
		// FIXME: unified path could be detected even when no controller is available
		return unifiedPath, errors.New("cannot detect unified path")
	}
	return unifiedPath, nil
}
func createCgroupsv2Path(path string) (Err error) {
	content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
	if err != nil {
		return err
	}
	if !filepath.HasPrefix(path, "/sys/fs/cgroup") {
		return fmt.Errorf("invalid cgroup path %s", path)
	}

	ctrs := bytes.Fields(content)
	res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)

	current := "/sys/fs"
	elements := strings.Split(path, "/")
	for i, e := range elements[3:] {
		current = filepath.Join(current, e)
		if i > 0 {
			if err := os.Mkdir(current, 0755); err != nil {
				if !os.IsExist(err) {
					return err
				}
			} else {
				// If the directory was created, be sure it is not left around on errors.
				defer func() {
					if Err != nil {
						os.Remove(current)
					}
				}()
			}
		}
		if i < len(elements[3:])-1 {
			if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0755); err != nil {
				return err
			}
		}
	}
	return nil
}

func joinCgroupsV2(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "memory")
	if err != nil {
		return err
	}
	return createCgroupsv2Path(path)
}

func (m *UnifiedManager) fsManager() (cgroups.Manager, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return fs2.NewManager(m.Cgroups, path, false)
}

func (m *UnifiedManager) Freeze(state configs.FreezerState) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Freeze(state)
}

func (m *UnifiedManager) GetPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetPids(path)
}

func (m *UnifiedManager) GetAllPids() ([]int, error) {
	path, err := m.GetUnifiedPath()
	if err != nil {
		return nil, err
	}
	return cgroups.GetAllPids(path)
}

func (m *UnifiedManager) GetStats() (*cgroups.Stats, error) {
	fsMgr, err := m.fsManager()
	if err != nil {
		return nil, err
	}
	return fsMgr.GetStats()
}

func (m *UnifiedManager) Set(container *configs.Config) error {
	fsMgr, err := m.fsManager()
	if err != nil {
		return err
	}
	return fsMgr.Set(container)
}

func (m *UnifiedManager) GetCgroups() (*configs.Cgroup, error) {
	return m.Cgroups, nil
}
<|endoftext|>


<commit_before>package mpb

import (
	"bytes"
	"testing"
	"unicode/utf8"
)

func TestDraw(t *testing.T) {
	// key is termWidth
	testSuite := map[int][]struct {
		name           string
		style          string
		total, current int64
		barWidth       int
		trimSpace      bool
		reverse        bool
		refill         int64
		want           string
	}{
		0: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "",
			},
		},
		1: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "",
			},
		},
		2: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[]",
			},
		},
		3: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[-]",
			},
		},
		4: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [] ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[>-]",
			},
		},
		5: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [-] ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[>--]",
			},
		},
		6: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>-] ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[>---]",
			},
		},
		7: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>--] ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[=>---]",
			},
		},
		8: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>---] ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[=>----]",
			},
		},
		80: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [========================>---------------------------------------------------] ",
			},
			{
				name:      "t,c{60,20}trim",
				total:     60,
				current:   20,
				trimSpace: true,
				want:      "[=========================>----------------------------------------------------]",
			},
			{
				name:     "t,c,bw{60,20,60}",
				total:    60,
				current:  20,
				barWidth: 60,
				want:     " [==================>---------------------------------------] ",
			},
			{
				name:      "t,c,bw{60,20,60}trim",
				total:     60,
				current:   20,
				barWidth:  60,
				trimSpace: true,
				want:      "[==================>---------------------------------------]",
			},
		},
		100: {
			{
				name:    "t,c{100,0}",
				total:   100,
				current: 0,
				want:    " [------------------------------------------------------------------------------------------------] ",
			},
			{
				name:      "t,c{100,0}trim",
				total:     100,
				current:   0,
				trimSpace: true,
				want:      "[--------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,1}",
				total:   100,
				current: 1,
				want:    " [>-----------------------------------------------------------------------------------------------] ",
			},
			{
				name:      "t,c{100,1}trim",
				total:     100,
				current:   1,
				trimSpace: true,
				want:      "[>-------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,33}",
				total:   100,
				current: 33,
				want:    " [===============================>----------------------------------------------------------------] ",
			},
			{
				name:      "t,c{100,33}trim",
				total:     100,
				current:   33,
				trimSpace: true,
				want:      "[===============================>------------------------------------------------------------------]",
			},
			{
				name:      "t,c{100,33}trim,rev",
				total:     100,
				current:   33,
				trimSpace: true,
				reverse:   true,
				want:      "[------------------------------------------------------------------<===============================]",
			},
			{
				name:    "t,c,refill{100,33,33}",
				total:   100,
				current: 33,
				refill:  33,
				want:    " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ",
			},
			{
				name:      "t,c,refill{100,33,33}trim",
				total:     100,
				current:   33,
				refill:    33,
				trimSpace: true,
				want:      "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]",
			},
			{
				name:      "t,c,refill{100,33,33}trim,rev",
				total:     100,
				current:   33,
				refill:    33,
				trimSpace: true,
				reverse:   true,
				want:      "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]",
			},
			{
				name:    "t,c,refill{100,40,33}",
				total:   100,
				current: 40,
				refill:  33,
				want:    " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ",
			},
			{
				name:      "t,c,refill{100,40,33}trim",
				total:     100,
				current:   40,
				refill:    33,
				trimSpace: true,
				want:      "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]",
			},
			{
				name:    "t,c{100,99}",
				total:   100,
				current: 99,
				want:    " [==============================================================================================>-] ",
			},
			{
				name:      "t,c{100,99}trim",
				total:     100,
				current:   99,
				trimSpace: true,
				want:      "[================================================================================================>-]",
			},
			{
				name:    "t,c{100,100}",
				total:   100,
				current: 100,
				want:    " [================================================================================================] ",
			},
			{
				name:      "t,c{100,100}trim",
				total:     100,
				current:   100,
				trimSpace: true,
				want:      "[==================================================================================================]",
			},
			{
				name:    "[=の-] t,c{100,1}",
				style:   "[=の-]",
				total:   100,
				current: 1,
				want:    " [の---------------------------------------------------------------------------------------------…] ",
			},
		},
	}

	var tmpBuf bytes.Buffer
	for tw, cases := range testSuite {
		for _, tc := range cases {
			s := newTestState(tc.style, tc.reverse)
			s.reqWidth = tc.barWidth
			s.total = tc.total
			s.current = tc.current
			s.trimSpace = tc.trimSpace
			if tc.refill > 0 {
				if f, ok := s.filler.(interface{ SetRefill(int64) }); ok {
					f.SetRefill(tc.refill)
				}
			}
			tmpBuf.Reset()
			tmpBuf.ReadFrom(s.draw(newStatistics(tw, s)))
			by := tmpBuf.Bytes()

			got := string(by[:len(by)-1])
			if !utf8.ValidString(got) {
				t.Fail()
			}
			if got != tc.want {
				t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got))
			}
		}
	}
}

func newTestState(style string, reverse bool) *bState {
	if style == "" {
		style = DefaultBarStyle
	}
	s := &bState{
		filler: NewBarFiller(style, reverse),
		bufP:   new(bytes.Buffer),
		bufB:   new(bytes.Buffer),
		bufA:   new(bytes.Buffer),
	}
	return s
}
<commit_msg>refactoring<commit_after>package mpb

import (
	"bytes"
	"testing"
	"unicode/utf8"
)

func TestDraw(t *testing.T) {
	// key is termWidth
	testSuite := map[int][]struct {
		name           string
		style          string
		total, current int64
		barWidth       int
		trim           bool
		reverse        bool
		refill         int64
		want           string
	}{
		0: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "",
			},
		},
		1: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "…  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "",
			},
		},
		2: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[]",
			},
		},
		3: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    "  ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[-]",
			},
		},
		4: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>-]",
			},
		},
		5: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [-] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>--]",
			},
		},
		6: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>-] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[>---]",
			},
		},
		7: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>--] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=>---]",
			},
		},
		8: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [>---] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=>----]",
			},
		},
		80: {
			{
				name:    "t,c{60,20}",
				total:   60,
				current: 20,
				want:    " [========================>---------------------------------------------------] ",
			},
			{
				name:    "t,c{60,20}trim",
				total:   60,
				current: 20,
				trim:    true,
				want:    "[=========================>----------------------------------------------------]",
			},
			{
				name:     "t,c,bw{60,20,60}",
				total:    60,
				current:  20,
				barWidth: 60,
				want:     " [==================>---------------------------------------] ",
			},
			{
				name:     "t,c,bw{60,20,60}trim",
				total:    60,
				current:  20,
				barWidth: 60,
				trim:     true,
				want:     "[==================>---------------------------------------]",
			},
		},
		100: {
			{
				name:    "t,c{100,0}",
				total:   100,
				current: 0,
				want:    " [------------------------------------------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,0}trim",
				total:   100,
				current: 0,
				trim:    true,
				want:    "[--------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,1}",
				total:   100,
				current: 1,
				want:    " [>-----------------------------------------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,1}trim",
				total:   100,
				current: 1,
				trim:    true,
				want:    "[>-------------------------------------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,33}",
				total:   100,
				current: 33,
				want:    " [===============================>----------------------------------------------------------------] ",
			},
			{
				name:    "t,c{100,33}trim",
				total:   100,
				current: 33,
				trim:    true,
				want:    "[===============================>------------------------------------------------------------------]",
			},
			{
				name:    "t,c{100,33}trim,rev",
				total:   100,
				current: 33,
				trim:    true,
				reverse: true,
				want:    "[------------------------------------------------------------------<===============================]",
			},
			{
				name:    "t,c,r{100,33,33}",
				total:   100,
				current: 33,
				refill:  33,
				want:    " [+++++++++++++++++++++++++++++++>----------------------------------------------------------------] ",
			},
			{
				name:    "t,c,r{100,33,33}trim",
				total:   100,
				current: 33,
				refill:  33,
				trim:    true,
				want:    "[+++++++++++++++++++++++++++++++>------------------------------------------------------------------]",
			},
			{
				name:    "t,c,r{100,33,33}trim,rev",
				total:   100,
				current: 33,
				refill:  33,
				trim:    true,
				reverse: true,
				want:    "[------------------------------------------------------------------<+++++++++++++++++++++++++++++++]",
			},
			{
				name:    "t,c,r{100,40,33}",
				total:   100,
				current: 40,
				refill:  33,
				want:    " [++++++++++++++++++++++++++++++++=====>----------------------------------------------------------] ",
			},
			{
				name:    "t,c,r{100,40,33}trim",
				total:   100,
				current: 40,
				refill:  33,
				trim:    true,
				want:    "[++++++++++++++++++++++++++++++++======>-----------------------------------------------------------]",
			},
			{
				name:    "t,c{100,99}",
				total:   100,
				current: 99,
				want:    " [==============================================================================================>-] ",
			},
			{
				name:    "t,c{100,99}trim",
				total:   100,
				current: 99,
				trim:    true,
				want:    "[================================================================================================>-]",
			},
			{
				name:    "t,c{100,100}",
				total:   100,
				current: 100,
				want:    " [================================================================================================] ",
			},
			{
				name:    "t,c{100,100}trim",
				total:   100,
				current: 100,
				trim:    true,
				want:    "[==================================================================================================]",
			},
			{
				name:    "[=の-] t,c{100,1}",
				style:   "[=の-]",
				total:   100,
				current: 1,
				want:    " [の---------------------------------------------------------------------------------------------…] ",
			},
		},
	}

	var tmpBuf bytes.Buffer
	for tw, cases := range testSuite {
		for _, tc := range cases {
			s := newTestState(tc.style, tc.reverse)
			s.reqWidth = tc.barWidth
			s.total = tc.total
			s.current = tc.current
			s.trimSpace = tc.trim
			if tc.refill > 0 {
				if f, ok := s.filler.(interface{ SetRefill(int64) }); ok {
					f.SetRefill(tc.refill)
				}
			}
			tmpBuf.Reset()
			tmpBuf.ReadFrom(s.draw(newStatistics(tw, s)))
			by := tmpBuf.Bytes()

			got := string(by[:len(by)-1])
			if !utf8.ValidString(got) {
				t.Fail()
			}
			if got != tc.want {
				t.Errorf("termWidth:%d %q want: %q %d, got: %q %d\n", tw, tc.name, tc.want, utf8.RuneCountInString(tc.want), got, utf8.RuneCountInString(got))
			}
		}
	}
}

func newTestState(style string, reverse bool) *bState {
	if style == "" {
		style = DefaultBarStyle
	}
	s := &bState{
		filler: NewBarFiller(style, reverse),
		bufP:   new(bytes.Buffer),
		bufB:   new(bytes.Buffer),
		bufA:   new(bytes.Buffer),
	}
	return s
}
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build js

package oto

import (
	"encoding/base64"
	"errors"
	"fmt"
	"sync"
	"syscall/js"
)

type driver struct {
	sampleRate      int
	channelNum      int
	bitDepthInBytes int
	nextPos         float64
	tmp             []byte
	bufferSize      int
	context         js.Value
	ready           bool
	callbacks       map[string]js.Func

	// For Audio Worklet
	workletNode js.Value
	bufs        [][]js.Value
	cond        *sync.Cond
}

type warn struct {
	msg string
}

func (w *warn) Error() string {
	return w.msg
}

const audioBufferSamples = 3200

func tryAudioWorklet(context js.Value, channelNum int) (js.Value, error) {
	if !js.Global().Get("AudioWorkletNode").Truthy() {
		return js.Undefined(), nil
	}

	worklet := context.Get("audioWorklet")
	if !worklet.Truthy() {
		return js.Undefined(), &warn{
			msg: "AudioWorklet is not available due to the insecure context. See https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet",
		}
	}

	script := `
class EbitenAudioWorkletProcessor extends AudioWorkletProcessor {
  constructor() {
    super();

    this.buffers_ = [[], []];
    this.offsets_ = [0, 0];
    this.offsetsInArray_ = [0, 0];
    this.consumed_ = [];

    this.port.onmessage = (e) => {
      const bufs = e.data;
      for (let ch = 0; ch < bufs.length; ch++) {
        this.buffers_[ch].push(bufs[ch]);
      }
    };
  }

  bufferTotalLength(ch) {
    const sum = this.buffers_[ch].reduce((total, buf) => total + buf.length, 0);
    return sum - this.offsetsInArray_[ch];
  }

  consume(ch, i) {
    while (this.buffers_[ch][0].length <= i - this.offsets_[ch]) {
      this.offsets_[ch] += this.buffers_[ch][0].length;
      this.offsetsInArray_[ch] = 0;
      const buf = this.buffers_[ch].shift();
      this.appendConsumedBuffer(ch, buf);
    }
    this.offsetsInArray_[ch]++;
    return this.buffers_[ch][0][i - this.offsets_[ch]];
  }

  appendConsumedBuffer(ch, buf) {
    let idx = this.consumed_.length - 1;
    if (idx < 0 || this.consumed_[idx][ch]) {
      this.consumed_.push([]);
      idx++;
    }
    this.consumed_[idx][ch] = buf;
  }

  process(inputs, outputs, parameters) {
    const out = outputs[0];

    if (this.bufferTotalLength(0) < out[0].length) {
      for (let ch = 0; ch < out.length; ch++) {
        for (let i = 0; i < out[ch].length; i++) {
          out[ch][i] = 0;
        }
      }
      return true;
    }

    for (let ch = 0; ch < out.length; ch++) {
      const offset = this.offsets_[ch] + this.offsetsInArray_[ch];
      for (let i = 0; i < out[ch].length; i++) {
        out[ch][i] = this.consume(ch, i + offset);
      }
    }

    for (let bufs of this.consumed_) {
      this.port.postMessage(bufs, bufs.map(buf => buf.buffer));
    }
    this.consumed_ = [];

    return true;
  }
}

registerProcessor('ebiten-audio-worklet-processor', EbitenAudioWorkletProcessor);`
	scriptURL := "data:application/javascript;base64," + base64.StdEncoding.EncodeToString([]byte(script))

	ch := make(chan error)
	worklet.Call("addModule", scriptURL).Call("then", js.FuncOf(func(js.Value, []js.Value) interface{} {
		close(ch)
		return nil
	})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		err := args[0]
		ch <- fmt.Errorf("oto: error at addModule: %s: %s", err.Get("name").String(), err.Get("message").String())
		close(ch)
		return nil
	}))
	if err := <-ch; err != nil {
		return js.Undefined(), err
	}

	options := js.Global().Get("Object").New()
	arr := js.Global().Get("Array").New()
	arr.Call("push", channelNum)
	options.Set("outputChannelCount", arr)

	node := js.Global().Get("AudioWorkletNode").New(context, "ebiten-audio-worklet-processor", options)
	node.Call("connect", context.Get("destination"))

	return node, nil
}

func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSize int) (tryWriteCloser, error) {
	class := js.Global().Get("AudioContext")
	if !class.Truthy() {
		class = js.Global().Get("webkitAudioContext")
	}
	if !class.Truthy() {
		return nil, errors.New("oto: audio couldn't be initialized")
	}

	options := js.Global().Get("Object").New()
	options.Set("sampleRate", sampleRate)
	context := class.New(options)

	node, err := tryAudioWorklet(context, channelNum)
	if err != nil {
		w, ok := err.(*warn)
		if !ok {
			return nil, err
		}
		js.Global().Get("console").Call("warn", w.Error())
	}

	bs := bufferSize
	if !node.Truthy() {
		bs = max(bufferSize, audioBufferSamples*channelNum*bitDepthInBytes)
	} else {
		bs = max(bufferSize, 4096)
	}

	p := &driver{
		sampleRate:      sampleRate,
		channelNum:      channelNum,
		bitDepthInBytes: bitDepthInBytes,
		context:         context,
		workletNode:     node,
		bufferSize:      bs,
		cond:            sync.NewCond(&sync.Mutex{}),
	}

	if node.Truthy() {
		s := p.bufferSize / p.channelNum / p.bitDepthInBytes / 2
		p.bufs = [][]js.Value{
			{
				js.Global().Get("Float32Array").New(s),
				js.Global().Get("Float32Array").New(s),
			},
			{
				js.Global().Get("Float32Array").New(s),
				js.Global().Get("Float32Array").New(s),
			},
		}

		node.Get("port").Set("onmessage", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			p.cond.L.Lock()
			defer p.cond.L.Unlock()

			bufs := args[0].Get("data")
			var arr []js.Value
			for i := 0; i < bufs.Length(); i++ {
				arr = append(arr, bufs.Index(i))
			}

			notify := len(p.bufs) == 0
			p.bufs = append(p.bufs, arr)
			if notify {
				p.cond.Signal()
			}

			return nil
		}))
	}

	setCallback := func(event string) js.Func {
		var f js.Func
		f = js.FuncOf(func(this js.Value, arguments []js.Value) interface{} {
			if !p.ready {
				p.context.Call("resume")
				p.ready = true
			}
			js.Global().Get("document").Call("removeEventListener", event, f)
			return nil
		})
		js.Global().Get("document").Call("addEventListener", event, f)
		p.callbacks[event] = f
		return f
	}

	// Browsers require user interaction to start the audio.
	// https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
	p.callbacks = map[string]js.Func{}
	setCallback("touchend")
	setCallback("keyup")
	setCallback("mouseup")
	return p, nil
}

func toLR(data []byte) ([]float32, []float32) {
	const max = 1 << 15

	l := make([]float32, len(data)/4)
	r := make([]float32, len(data)/4)
	for i := 0; i < len(data)/4; i++ {
		l[i] = float32(int16(data[4*i])|int16(data[4*i+1])<<8) / max
		r[i] = float32(int16(data[4*i+2])|int16(data[4*i+3])<<8) / max
	}
	return l, r
}

func (p *driver) TryWrite(data []byte) (int, error) {
	if !p.ready {
		return 0, nil
	}

	if p.workletNode.Truthy() {
		p.cond.L.Lock()
		defer p.cond.L.Unlock()

		n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
		p.tmp = append(p.tmp, data[:n]...)

		if len(p.tmp) < p.bufferSize/2 {
			return n, nil
		}

		for len(p.bufs) == 0 {
			p.cond.Wait()
		}

		l, r := toLR(p.tmp[:p.bufferSize/2])
		tl := p.bufs[0][0]
		tr := p.bufs[0][1]
		copyFloat32sToJS(tl, l)
		copyFloat32sToJS(tr, r)
		p.tmp = p.tmp[p.bufferSize/2:]

		bufs := js.Global().Get("Array").New()
		bufs.Call("push", tl, tr)
		transfers := js.Global().Get("Array").New()
		transfers.Call("push", tl.Get("buffer"), tr.Get("buffer"))

		p.workletNode.Get("port").Call("postMessage", bufs, transfers)

		p.bufs = p.bufs[1:]

		return n, nil
	}

	n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
	p.tmp = append(p.tmp, data[:n]...)

	c := p.context.Get("currentTime").Float()

	if p.nextPos < c {
		p.nextPos = c
	}

	// It's too early to enqueue a buffer.
	// Highly likely, there are two playing buffers now.
	if c+float64(p.bufferSize/p.bitDepthInBytes/p.channelNum)/float64(p.sampleRate) < p.nextPos {
		return n, nil
	}

	le := audioBufferSamples * p.bitDepthInBytes * p.channelNum
	if len(p.tmp) < le {
		return n, nil
	}

	buf := p.context.Call("createBuffer", p.channelNum, audioBufferSamples, p.sampleRate)
	l, r := toLR(p.tmp[:le])
	tl, freel := float32SliceToTypedArray(l)
	tr, freer := float32SliceToTypedArray(r)
	if buf.Get("copyToChannel").Truthy() {
		buf.Call("copyToChannel", tl, 0, 0)
		buf.Call("copyToChannel", tr, 1, 0)
	} else {
		// copyToChannel is not defined on Safari 11
		buf.Call("getChannelData", 0).Call("set", tl)
		buf.Call("getChannelData", 1).Call("set", tr)
	}
	freel()
	freer()

	s := p.context.Call("createBufferSource")
	s.Set("buffer", buf)
	s.Call("connect", p.context.Get("destination"))
	s.Call("start", p.nextPos)
	p.nextPos += buf.Get("duration").Float()

	p.tmp = p.tmp[le:]
	return n, nil
}

func (p *driver) Close() error {
	for event, f := range p.callbacks {
		// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
		// "Calling removeEventListener() with arguments that do not identify any currently registered EventListener on the EventTarget has no effect."
		js.Global().Get("document").Call("removeEventListener", event, f)
		f.Release()
	}
	p.callbacks = nil
	return nil
}
<commit_msg>js: Reuse existing arrays<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build js

package oto

import (
	"encoding/base64"
	"errors"
	"fmt"
	"sync"
	"syscall/js"
)

type driver struct {
	sampleRate      int
	channelNum      int
	bitDepthInBytes int
	nextPos         float64
	tmp             []byte
	bufferSize      int
	context         js.Value
	ready           bool
	callbacks       map[string]js.Func

	// For Audio Worklet
	workletNode   js.Value
	messageArray  js.Value
	transferArray js.Value
	bufs          [][]js.Value
	cond          *sync.Cond
}

type warn struct {
	msg string
}

func (w *warn) Error() string {
	return w.msg
}

const audioBufferSamples = 3200

func tryAudioWorklet(context js.Value, channelNum int) (js.Value, error) {
	if !js.Global().Get("AudioWorkletNode").Truthy() {
		return js.Undefined(), nil
	}

	worklet := context.Get("audioWorklet")
	if !worklet.Truthy() {
		return js.Undefined(), &warn{
			msg: "AudioWorklet is not available due to the insecure context. See https://developer.mozilla.org/en-US/docs/Web/API/AudioWorklet",
		}
	}

	script := `
class EbitenAudioWorkletProcessor extends AudioWorkletProcessor {
  constructor() {
    super();

    this.buffers_ = [[], []];
    this.offsets_ = [0, 0];
    this.offsetsInArray_ = [0, 0];
    this.consumed_ = [];

    this.port.onmessage = (e) => {
      const bufs = e.data;
      for (let ch = 0; ch < bufs.length; ch++) {
        this.buffers_[ch].push(bufs[ch]);
      }
    };
  }

  bufferTotalLength(ch) {
    const sum = this.buffers_[ch].reduce((total, buf) => total + buf.length, 0);
    return sum - this.offsetsInArray_[ch];
  }

  consume(ch, i) {
    while (this.buffers_[ch][0].length <= i - this.offsets_[ch]) {
      this.offsets_[ch] += this.buffers_[ch][0].length;
      this.offsetsInArray_[ch] = 0;
      const buf = this.buffers_[ch].shift();
      this.appendConsumedBuffer(ch, buf);
    }
    this.offsetsInArray_[ch]++;
    return this.buffers_[ch][0][i - this.offsets_[ch]];
  }

  appendConsumedBuffer(ch, buf) {
    let idx = this.consumed_.length - 1;
    if (idx < 0 || this.consumed_[idx][ch]) {
      this.consumed_.push([]);
      idx++;
    }
    this.consumed_[idx][ch] = buf;
  }

  process(inputs, outputs, parameters) {
    const out = outputs[0];

    if (this.bufferTotalLength(0) < out[0].length) {
      for (let ch = 0; ch < out.length; ch++) {
        for (let i = 0; i < out[ch].length; i++) {
          out[ch][i] = 0;
        }
      }
      return true;
    }

    for (let ch = 0; ch < out.length; ch++) {
      const offset = this.offsets_[ch] + this.offsetsInArray_[ch];
      for (let i = 0; i < out[ch].length; i++) {
        out[ch][i] = this.consume(ch, i + offset);
      }
    }

    for (let bufs of this.consumed_) {
      this.port.postMessage(bufs, bufs.map(buf => buf.buffer));
    }
    this.consumed_ = [];

    return true;
  }
}

registerProcessor('ebiten-audio-worklet-processor', EbitenAudioWorkletProcessor);`
	scriptURL := "data:application/javascript;base64," + base64.StdEncoding.EncodeToString([]byte(script))

	ch := make(chan error)
	worklet.Call("addModule", scriptURL).Call("then", js.FuncOf(func(js.Value, []js.Value) interface{} {
		close(ch)
		return nil
	})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		err := args[0]
		ch <- fmt.Errorf("oto: error at addModule: %s: %s", err.Get("name").String(), err.Get("message").String())
		close(ch)
		return nil
	}))
	if err := <-ch; err != nil {
		return js.Undefined(), err
	}

	options := js.Global().Get("Object").New()
	arr := js.Global().Get("Array").New()
	arr.Call("push", channelNum)
	options.Set("outputChannelCount", arr)

	node := js.Global().Get("AudioWorkletNode").New(context, "ebiten-audio-worklet-processor", options)
	node.Call("connect", context.Get("destination"))

	return node, nil
}

func newDriver(sampleRate, channelNum, bitDepthInBytes, bufferSize int) (tryWriteCloser, error) {
	class := js.Global().Get("AudioContext")
	if !class.Truthy() {
		class = js.Global().Get("webkitAudioContext")
	}
	if !class.Truthy() {
		return nil, errors.New("oto: audio couldn't be initialized")
	}

	options := js.Global().Get("Object").New()
	options.Set("sampleRate", sampleRate)
	context := class.New(options)

	node, err := tryAudioWorklet(context, channelNum)
	if err != nil {
		w, ok := err.(*warn)
		if !ok {
			return nil, err
		}
		js.Global().Get("console").Call("warn", w.Error())
	}

	bs := bufferSize
	if !node.Truthy() {
		bs = max(bufferSize, audioBufferSamples*channelNum*bitDepthInBytes)
	} else {
		bs = max(bufferSize, 4096)
	}

	p := &driver{
		sampleRate:      sampleRate,
		channelNum:      channelNum,
		bitDepthInBytes: bitDepthInBytes,
		context:         context,
		workletNode:     node,
		bufferSize:      bs,
	}

	if node.Truthy() {
		p.messageArray = js.Global().Get("Array").New(2)
		p.transferArray = js.Global().Get("Array").New(2)
		p.cond = sync.NewCond(&sync.Mutex{})

		s := p.bufferSize / p.channelNum / p.bitDepthInBytes / 2
		p.bufs = [][]js.Value{
			{
				js.Global().Get("Float32Array").New(s),
				js.Global().Get("Float32Array").New(s),
			},
			{
				js.Global().Get("Float32Array").New(s),
				js.Global().Get("Float32Array").New(s),
			},
		}

		node.Get("port").Set("onmessage", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
			p.cond.L.Lock()
			defer p.cond.L.Unlock()

			bufs := args[0].Get("data")
			var arr []js.Value
			for i := 0; i < bufs.Length(); i++ {
				arr = append(arr, bufs.Index(i))
			}

			notify := len(p.bufs) == 0
			p.bufs = append(p.bufs, arr)
			if notify {
				p.cond.Signal()
			}

			return nil
		}))
	}

	setCallback := func(event string) js.Func {
		var f js.Func
		f = js.FuncOf(func(this js.Value, arguments []js.Value) interface{} {
			if !p.ready {
				p.context.Call("resume")
				p.ready = true
			}
			js.Global().Get("document").Call("removeEventListener", event, f)
			return nil
		})
		js.Global().Get("document").Call("addEventListener", event, f)
		p.callbacks[event] = f
		return f
	}

	// Browsers require user interaction to start the audio.
	// https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
	p.callbacks = map[string]js.Func{}
	setCallback("touchend")
	setCallback("keyup")
	setCallback("mouseup")
	return p, nil
}

func toLR(data []byte) ([]float32, []float32) {
	const max = 1 << 15

	l := make([]float32, len(data)/4)
	r := make([]float32, len(data)/4)
	for i := 0; i < len(data)/4; i++ {
		l[i] = float32(int16(data[4*i])|int16(data[4*i+1])<<8) / max
		r[i] = float32(int16(data[4*i+2])|int16(data[4*i+3])<<8) / max
	}
	return l, r
}

func (p *driver) TryWrite(data []byte) (int, error) {
	if !p.ready {
		return 0, nil
	}

	if p.workletNode.Truthy() {
		p.cond.L.Lock()
		defer p.cond.L.Unlock()

		n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
		p.tmp = append(p.tmp, data[:n]...)

		if len(p.tmp) < p.bufferSize/2 {
			return n, nil
		}

		for len(p.bufs) == 0 {
			p.cond.Wait()
		}

		l, r := toLR(p.tmp[:p.bufferSize/2])
		tl := p.bufs[0][0]
		tr := p.bufs[0][1]
		copyFloat32sToJS(tl, l)
		copyFloat32sToJS(tr, r)
		p.tmp = p.tmp[p.bufferSize/2:]

		bufs := p.messageArray
		bufs.SetIndex(0, tl)
		bufs.SetIndex(1, tr)
		transfers := p.transferArray
		transfers.SetIndex(0, tl.Get("buffer"))
		transfers.SetIndex(1, tr.Get("buffer"))

		p.workletNode.Get("port").Call("postMessage", bufs, transfers)

		p.bufs = p.bufs[1:]

		return n, nil
	}

	n := min(len(data), max(0, p.bufferSize-len(p.tmp)))
	p.tmp = append(p.tmp, data[:n]...)

	c := p.context.Get("currentTime").Float()

	if p.nextPos < c {
		p.nextPos = c
	}

	// It's too early to enqueue a buffer.
	// Highly likely, there are two playing buffers now.
	if c+float64(p.bufferSize/p.bitDepthInBytes/p.channelNum)/float64(p.sampleRate) < p.nextPos {
		return n, nil
	}

	le := audioBufferSamples * p.bitDepthInBytes * p.channelNum
	if len(p.tmp) < le {
		return n, nil
	}

	buf := p.context.Call("createBuffer", p.channelNum, audioBufferSamples, p.sampleRate)
	l, r := toLR(p.tmp[:le])
	tl, freel := float32SliceToTypedArray(l)
	tr, freer := float32SliceToTypedArray(r)
	if buf.Get("copyToChannel").Truthy() {
		buf.Call("copyToChannel", tl, 0, 0)
		buf.Call("copyToChannel", tr, 1, 0)
	} else {
		// copyToChannel is not defined on Safari 11
		buf.Call("getChannelData", 0).Call("set", tl)
		buf.Call("getChannelData", 1).Call("set", tr)
	}
	freel()
	freer()

	s := p.context.Call("createBufferSource")
	s.Set("buffer", buf)
	s.Call("connect", p.context.Get("destination"))
	s.Call("start", p.nextPos)
	p.nextPos += buf.Get("duration").Float()

	p.tmp = p.tmp[le:]
	return n, nil
}

func (p *driver) Close() error {
	for event, f := range p.callbacks {
		// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
		// "Calling removeEventListener() with arguments that do not identify any currently registered EventListener on the EventTarget has no effect."
		js.Global().Get("document").Call("removeEventListener", event, f)
		f.Release()
	}
	p.callbacks = nil
	return nil
}
<|endoftext|>


<commit_before>package dhcpv6

import (
	"bytes"
	"net"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
)

func TestDUIDTypeString(t *testing.T) {
	tests := []struct {
		in  DUIDType
		out string
	}{
		{0, "Unknown"},
		{DUIDTypeLLT, "LinkLayerTime"},
		{DUIDTypeEN, "Enterprise Number"},
		{DUIDTypeLL, "LinkLayer"},
		{DUIDTypeUUID, "UUID"},
	}

	for _, test := range tests {
		if strings.Compare(test.in.String(), test.out) != 0 {
			t.Errorf("expected %s but got %s", test.out, test.in.String())
		}
	}
}

func TestDUIDDecode(t *testing.T) {
	// test decoding too few bytes
	if _, err := DecodeDUID([]byte{0}); err == nil {
		t.Error("expected error while decoding too short DUID")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test decoding unknown DUIDType
	if _, err := DecodeDUID([]byte{0, 255}); err == nil {
		t.Error("expected error while decoding unknown DUIDType")
	} else if err.Error() != "unhandled DUIDType Unknown" {
		t.Errorf("unexpected error: %s", err)
	}
}

func TestDuidLLT(t *testing.T) {
	// test decoding bytes to DUIDLLT
	fixtbyte := []byte{0, 1, 0, 1, 29, 205, 101, 0, 170, 187, 204, 221, 238, 255}
	duid, err := DecodeDUID(fixtbyte)
	if err != nil {
		t.Errorf("error decoding fixture: %s", err)
	}

	duidllt := duid.(*DUIDLLT)
	// check contents of duid
	if duidllt.Type() != DUIDTypeLLT {
		t.Errorf("expected duid type %d, got %d", DUIDTypeLLT, duidllt.Type())
	}
	fixthwtype := uint16(1)
	if duidllt.HardwareType != fixthwtype {
		t.Errorf("expected hw type %d, got %d", fixthwtype, duidllt.HardwareType)
	}
	// fixture with offset of 30 years
	fixttime := time.Unix(1446771200, 0)
	if !duidllt.Time.Equal(fixttime) {
		t.Errorf("expected time %s, got %s", fixttime, duidllt.Time)
	}
	fixtmac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
	if bytes.Compare(duidllt.LinkLayerAddress, fixtmac) != 0 {
		t.Errorf("expected lla %s, got %s", fixtmac, duidllt.LinkLayerAddress)
	}

	// test for error when decoding too small DUIDLLT
	if _, err := DecodeDUID(fixtbyte[:7]); err == nil {
		t.Error("expected error decoding too small DUIDLLT")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test matching output for String()
	fixtstr := "hwaddr/time type 1 time 500000000 aa:bb:cc:dd:ee:ff"
	if duidllt.String() != fixtstr {
		t.Errorf("unexpected String() output: %s", duidllt.String())
	}

	// test if marshalled bytes match fixture
	if mshByte, err := duidllt.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}

	// recreate same struct and see if its marshal matches fixture
	duidllt = &DUIDLLT{
		HardwareType:     fixthwtype,
		Time:             fixttime,
		LinkLayerAddress: fixtmac,
	}
	if mshByte, err := duidllt.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}
}

func TestDuidLL(t *testing.T) {
	// test decoding bytes to DUIDLL
	fixtbyte := []byte{0, 3, 0, 1, 170, 187, 204, 221, 238, 255}
	duid, err := DecodeDUID(fixtbyte)
	if err != nil {
		t.Errorf("error decoding fixture: %s", err)
	}

	duidll := duid.(*DUIDLL)
	// check contents of duid
	if duidll.Type() != DUIDTypeLL {
		t.Errorf("expected duid type %d, got %d", DUIDTypeLL, duidll.Type())
	}
	fixthwtype := uint16(1)
	if duidll.HardwareType != fixthwtype {
		t.Errorf("expected hw type %d, got %d", fixthwtype, duidll.HardwareType)
	}
	fixtmac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
	if bytes.Compare(duidll.LinkLayerAddress, fixtmac) != 0 {
		t.Errorf("expected lla %s, got %s", fixtmac, duidll.LinkLayerAddress)
	}

	// test for error when decoding too small DUIDLL
	if _, err := DecodeDUID(fixtbyte[:3]); err == nil {
		t.Error("expected error decoding too small DUIDLL")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test matching output for String()
	fixtstr := "hwaddr type 3 aa:bb:cc:dd:ee:ff"
	if duidll.String() != fixtstr {
		t.Errorf("unexpected String() output: %s", duidll.String())
	}

	// test if marshalled bytes match fixture
	if mshByte, err := duidll.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}

	// recreate same struct and see if it's marshal matches fixture
	duidll = &DUIDLL{
		HardwareType:     fixthwtype,
		LinkLayerAddress: fixtmac,
	}
	if mshByte, err := duidll.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}
}

func TestDuidUUID(t *testing.T) {
	// test decoding bytes to DUIDUUID
	fixtbyte := []byte{0, 4, 126, 102, 234, 162, 230, 221, 73, 123, 142, 33, 49, 148, 75, 40, 43, 67}
	duid, err := DecodeDUID(fixtbyte)
	if err != nil {
		t.Errorf("error decoding fixture: %s", err)
	}

	duiduuid := duid.(*DUIDUUID)
	// check contents of duid
	if duiduuid.Type() != DUIDTypeUUID {
		t.Errorf("expected duid type %d, got %d", DUIDTypeUUID, duiduuid.Type())
	}

	fixtuuid := "7e66eaa2-e6dd-497b-8e21-31944b282b43"
	if fixtuuid != duiduuid.UUID.String() {
		t.Errorf("expected UUID %s, got %s", fixtuuid, duiduuid.UUID.String())
	}

	// test for error when decoding too small DUIDUUID
	if _, err = DecodeDUID(fixtbyte[:17]); err == nil {
		t.Error("expected error decoding too small DUIDUUID")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test matching output for String()
	fixtstr := "type 4"
	if duiduuid.String() != fixtstr {
		t.Errorf("unexpected String() output: %s", duiduuid.String())
	}

	// test matching output for Len()
	fixtlen := uint16(18)
	if duiduuid.Len() != fixtlen {
		t.Errorf("expected Len of %d, got %d", fixtlen, duiduuid.Len())
	}

	// test if marshalled bytes match fixture
	if mshByte, merr := duiduuid.Marshal(); merr != nil {
		t.Errorf("error marshalling DUID: %s", merr)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}

	// recreate same struct and see if it's marshal matches fixture
	duiduuid = &DUIDUUID{}
	if duiduuid.UUID, err = uuid.Parse(fixtuuid); err != nil {
		t.Errorf("error parsing UUID: %s", err)
	}
	if mshByte, err := duiduuid.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}
}
<commit_msg>added tests for Len()<commit_after>package dhcpv6

import (
	"bytes"
	"net"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
)

func TestDUIDTypeString(t *testing.T) {
	tests := []struct {
		in  DUIDType
		out string
	}{
		{0, "Unknown"},
		{DUIDTypeLLT, "LinkLayerTime"},
		{DUIDTypeEN, "Enterprise Number"},
		{DUIDTypeLL, "LinkLayer"},
		{DUIDTypeUUID, "UUID"},
	}

	for _, test := range tests {
		if strings.Compare(test.in.String(), test.out) != 0 {
			t.Errorf("expected %s but got %s", test.out, test.in.String())
		}
	}
}

func TestDUIDDecode(t *testing.T) {
	// test decoding too few bytes
	if _, err := DecodeDUID([]byte{0}); err == nil {
		t.Error("expected error while decoding too short DUID")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test decoding unknown DUIDType
	if _, err := DecodeDUID([]byte{0, 255}); err == nil {
		t.Error("expected error while decoding unknown DUIDType")
	} else if err.Error() != "unhandled DUIDType Unknown" {
		t.Errorf("unexpected error: %s", err)
	}
}

func TestDuidLLT(t *testing.T) {
	// test decoding bytes to DUIDLLT
	fixtbyte := []byte{0, 1, 0, 1, 29, 205, 101, 0, 170, 187, 204, 221, 238, 255}
	duid, err := DecodeDUID(fixtbyte)
	if err != nil {
		t.Errorf("error decoding fixture: %s", err)
	}

	duidllt := duid.(*DUIDLLT)
	// check contents of duid
	if duidllt.Type() != DUIDTypeLLT {
		t.Errorf("expected duid type %d, got %d", DUIDTypeLLT, duidllt.Type())
	}
	fixthwtype := uint16(1)
	if duidllt.HardwareType != fixthwtype {
		t.Errorf("expected hw type %d, got %d", fixthwtype, duidllt.HardwareType)
	}
	// fixture with offset of 30 years
	fixttime := time.Unix(1446771200, 0)
	if !duidllt.Time.Equal(fixttime) {
		t.Errorf("expected time %s, got %s", fixttime, duidllt.Time)
	}
	fixtmac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
	if bytes.Compare(duidllt.LinkLayerAddress, fixtmac) != 0 {
		t.Errorf("expected lla %s, got %s", fixtmac, duidllt.LinkLayerAddress)
	}

	// test for error when decoding too small DUIDLLT
	if _, err := DecodeDUID(fixtbyte[:7]); err == nil {
		t.Error("expected error decoding too small DUIDLLT")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test matching output for String()
	fixtstr := "hwaddr/time type 1 time 500000000 aa:bb:cc:dd:ee:ff"
	if duidllt.String() != fixtstr {
		t.Errorf("unexpected String() output: %s", duidllt.String())
	}

	// test matching output for Len()
	fixtlen := uint16(14)
	if duidllt.Len() != fixtlen {
		t.Errorf("expected Len of %d, got %d", fixtlen, duidllt.Len())
	}

	// test if marshalled bytes match fixture
	if mshByte, err := duidllt.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}

	// recreate same struct and see if its marshal matches fixture
	duidllt = &DUIDLLT{
		HardwareType:     fixthwtype,
		Time:             fixttime,
		LinkLayerAddress: fixtmac,
	}
	if mshByte, err := duidllt.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}
}

func TestDuidLL(t *testing.T) {
	// test decoding bytes to DUIDLL
	fixtbyte := []byte{0, 3, 0, 1, 170, 187, 204, 221, 238, 255}
	duid, err := DecodeDUID(fixtbyte)
	if err != nil {
		t.Errorf("error decoding fixture: %s", err)
	}

	duidll := duid.(*DUIDLL)
	// check contents of duid
	if duidll.Type() != DUIDTypeLL {
		t.Errorf("expected duid type %d, got %d", DUIDTypeLL, duidll.Type())
	}
	fixthwtype := uint16(1)
	if duidll.HardwareType != fixthwtype {
		t.Errorf("expected hw type %d, got %d", fixthwtype, duidll.HardwareType)
	}
	fixtmac, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
	if bytes.Compare(duidll.LinkLayerAddress, fixtmac) != 0 {
		t.Errorf("expected lla %s, got %s", fixtmac, duidll.LinkLayerAddress)
	}

	// test for error when decoding too small DUIDLL
	if _, err := DecodeDUID(fixtbyte[:3]); err == nil {
		t.Error("expected error decoding too small DUIDLL")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test matching output for String()
	fixtstr := "hwaddr type 3 aa:bb:cc:dd:ee:ff"
	if duidll.String() != fixtstr {
		t.Errorf("unexpected String() output: %s", duidll.String())
	}

	// test matching output for Len()
	fixtlen := uint16(10)
	if duidll.Len() != fixtlen {
		t.Errorf("expected Len of %d, got %d", fixtlen, duidll.Len())
	}

	// test if marshalled bytes match fixture
	if mshByte, err := duidll.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}

	// recreate same struct and see if it's marshal matches fixture
	duidll = &DUIDLL{
		HardwareType:     fixthwtype,
		LinkLayerAddress: fixtmac,
	}
	if mshByte, err := duidll.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}
}

func TestDuidUUID(t *testing.T) {
	// test decoding bytes to DUIDUUID
	fixtbyte := []byte{0, 4, 126, 102, 234, 162, 230, 221, 73, 123, 142, 33, 49, 148, 75, 40, 43, 67}
	duid, err := DecodeDUID(fixtbyte)
	if err != nil {
		t.Errorf("error decoding fixture: %s", err)
	}

	duiduuid := duid.(*DUIDUUID)
	// check contents of duid
	if duiduuid.Type() != DUIDTypeUUID {
		t.Errorf("expected duid type %d, got %d", DUIDTypeUUID, duiduuid.Type())
	}

	fixtuuid := "7e66eaa2-e6dd-497b-8e21-31944b282b43"
	if fixtuuid != duiduuid.UUID.String() {
		t.Errorf("expected UUID %s, got %s", fixtuuid, duiduuid.UUID.String())
	}

	// test for error when decoding too small DUIDUUID
	if _, err = DecodeDUID(fixtbyte[:17]); err == nil {
		t.Error("expected error decoding too small DUIDUUID")
	} else if err != errDUIDTooShort {
		t.Errorf("unexpected error: %s", err)
	}

	// test matching output for String()
	fixtstr := "type 4"
	if duiduuid.String() != fixtstr {
		t.Errorf("unexpected String() output: %s", duiduuid.String())
	}

	// test matching output for Len()
	fixtlen := uint16(18)
	if duiduuid.Len() != fixtlen {
		t.Errorf("expected Len of %d, got %d", fixtlen, duiduuid.Len())
	}

	// test if marshalled bytes match fixture
	if mshByte, merr := duiduuid.Marshal(); merr != nil {
		t.Errorf("error marshalling DUID: %s", merr)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}

	// recreate same struct and see if it's marshal matches fixture
	duiduuid = &DUIDUUID{}
	if duiduuid.UUID, err = uuid.Parse(fixtuuid); err != nil {
		t.Errorf("error parsing UUID: %s", err)
	}
	if mshByte, err := duiduuid.Marshal(); err != nil {
		t.Errorf("error marshalling DUID: %s", err)
	} else if bytes.Compare(fixtbyte, mshByte) != 0 {
		t.Errorf("marshalled DUID didn't match fixture!\nfixture: %v\nmarshal: %v", fixtbyte, mshByte)
	}
}
<|endoftext|>


<commit_before>/*
 * (c) 2013, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"expvar"
	"hash"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var fmap = template.FuncMap{
	"html": template.HTMLEscaper,
	"url":  UserInputFormatter,
}

func UserInputFormatter(v ...interface{}) string {
	return template.HTMLEscapeString(url.QueryEscape(v[0].(string)))
}

// Statistics.
var numRequests *expvar.Int = expvar.NewInt("num-http-requests")
var numSubmitted *expvar.Int = expvar.NewInt("num-successful-form-submissions")
var numSubmitErrors *expvar.Map = expvar.NewMap("num-form-submission-errors")

// Regular expressions for verification of the email and phone number fields.
var emailRe *regexp.Regexp
var phoneRe *regexp.Regexp

// Data type for the HTTP handler which takes the requests. We require the
// templates and a passthrough object for static content requests, so we
// need to hold some state.
type FormInputHandler struct {
	applicationTmpl *template.Template
	printTmpl       *template.Template
	passthrough     http.Handler
}

// Data used by the HTML template. Contains not just data entered so far,
// but also some error texts in case there was a problem submitting data.
type FormInputData struct {
	MemberData *Member
	Comment    string
	CommonErr  string
	FieldErr   map[string]string
}

// Parse the form data from the membership signup form and verify that it
// can be considered acceptable. If the data looks correct, return the
// print template for the user to sign and send in.
func (self *FormInputHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var err error
	var data FormInputData
	var fee float64
	var ok bool = true
	numRequests.Add(1)

	// Pass JavaScript and CSS requests through to the passthrough handler.
	if strings.HasPrefix(req.URL.Path, "/css/") ||
		strings.HasPrefix(req.URL.Path, "/js/") ||
		req.URL.Path == "/favicon.ico" {
		self.passthrough.ServeHTTP(w, req)
		return
	}

	data.FieldErr = make(map[string]string)

	if err = req.ParseForm(); err != nil {
		data.CommonErr = err.Error()
		numSubmitErrors.Add(err.Error(), 1)
		self.applicationTmpl.Execute(w, data)
		return
	}

	// No data entered: the user is probably just going to the web site
	// for the first time, so data validation is useless.
	if len(req.PostForm) == 0 {
		self.applicationTmpl.Execute(w, data)
		return
	}

	// You might think that it would be a good idea to split the name
	// field into first and last name, which might even work for this
	// specific, localized use case, but it is bad practice, because
	// some countries don't have the concept of last names, and would
	// set a bad precedent for people reading and using this code.
	var name string = req.PostFormValue("mr[name]")
	if len(name) <= 0 {
		numSubmitErrors.Add("no-name", 1)
		data.FieldErr["name"] = "Ein Name ist erforderlich"
	} else {
		data.MemberData.Name = &name
	}

	// The same applies to the address. There just is no globally common
	// format for home addresses, not everything has a house number, and
	// we don't want to encourage people to think so.
	var address string = req.PostFormValue("mr[address]")
	if len(address) <= 0 {
		numSubmitErrors.Add("no-street", 1)
		data.FieldErr["address"] = "Eine Adresse ist erforderlich"
	} else {
		data.MemberData.Street = &address
	}

	var city string = req.PostFormValue("mr[city]")
	if len(city) <= 0 {
		numSubmitErrors.Add("no-city", 1)
		data.FieldErr["city"] = "Ein Wohnort ist erforderlich"
	} else {
		data.MemberData.City = &city
	}

	// As for zip codes, you don't even have to go to India to find out
	// why enforcing a format there doesn't work. Many people around
	// here believe that a 4-5 digit number is enough to represent a zip
	// code. But then the Netherlands have zip codes like «4201 EB»
	// (where EB is part of the zip code). If you consider adding an
	// exception for this, please note that British zip codes look like
	// «G1 1PP». The only realistic way to deal with these is to allow
	// free text for zip codes.
	var zip string = req.PostFormValue("mr[zip]")
	if len(zip) <= 0 {
		numSubmitErrors.Add("no-zip", 1)
		data.FieldErr["zip"] = "Eine Postleitzahl ist erforderlich"
	} else {
		data.MemberData.Zipcode = &zip
	}

	// The country could arguably be a list.
	var country string = req.PostFormValue("mr[country]")
	if len(country) <= 0 {
		numSubmitErrors.Add("no-country", 1)
		data.FieldErr["country"] = "Ein Wohnland ist erforderlich"
	} else {
		data.MemberData.Country = &country
	}

	var email string = req.PostFormValue("mr[email]")
	if !emailRe.MatchString(email) {
		if len(email) > 0 {
			data.FieldErr["email"] = "Mailadresse sollte im Format a@b.ch sein"
			numSubmitErrors.Add("bad-email-format", 1)
		} else {
			data.FieldErr["email"] = "Muss angegeben werden"
			numSubmitErrors.Add("no-email", 1)
		}
		ok = false
	} else {
		data.MemberData.Email = &email
	}

	var phone string = req.PostFormValue("mr[telephone]")
	if len(phone) > 0 && !phoneRe.MatchString(phone) {
		data.FieldErr["telephone"] = "Telephonnummer sollte im Format +41 79 123 45 67 sein"
		numSubmitErrors.Add("bad-phone-format", 1)
		ok = false
	} else {
		data.MemberData.Phone = &phone
	}

	// TODO(caoimhe): Verify the user name field.
	var username string = req.PostFormValue("mr[username]")
	if len(username) > 0 {
		data.MemberData.Username = &username
	}

	var pw string = req.PostFormValue("mr[password]")
	if pw != req.PostFormValue("mr[passwordConfirm]") {
		data.FieldErr["password"] = "Passworte stimmen nicht überein"
		numSubmitErrors.Add("password-mismatch", 1)
		ok = false
	} else {
		var h hash.Hash = sha1.New()
		h.Write([]byte(pw))
		pw = "{SHA}" + base64.StdEncoding.EncodeToString(h.Sum([]byte{}))
		data.MemberData.Pwhash = &pw
	}

	if req.PostFormValue("mr[statutes]") != "accepted" {
		data.FieldErr["statutes"] = "Statuten müssen akzeptiert werden"
		numSubmitErrors.Add("statutes-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[ipay]") != "accepted" {
		data.FieldErr["ipay"] = "Zahlungsbereitschaft ist notwendig"
		numSubmitErrors.Add("payment-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[rules]") != "accepted" {
		data.FieldErr["rules"] = "Reglement muss akzeptiert werden"
		numSubmitErrors.Add("rules-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[gt18]") != "yes" {
		data.FieldErr["gt18"] = "Man muss mindestens 18 Jahre sein, um uns beizutreten"
		numSubmitErrors.Add("not-gt18", 1)
		ok = false
	}

	if len(req.PostFormValue("mr[customFee]")) > 0 {
		fee, err = strconv.ParseFloat(req.PostFormValue("mr[customFee]"), 64)
		if err == strconv.ErrRange {
			data.FieldErr["customFee"] = "Der Betrag ist irgendwie etwas gross/klein, oder?"
			numSubmitErrors.Add("fee-out-of-range", 1)
			ok = false
		} else if err == strconv.ErrSyntax {
			data.FieldErr["customFee"] = "Der Mitgliedsbeitrag kann nicht als Zahl identifiziert werden"
			numSubmitErrors.Add("fee-not-a-number", 1)
			log.Print("Unable to parse ", req.PostFormValue("mr[customFee]"),
				" as a valid fee")
			ok = false
		} else if err != nil {
			// No idea? This shouldn't really happen.
			data.FieldErr["customFee"] = err.Error()
			log.Print("Error converting ", req.PostFormValue("mr[customFee]"),
				" to a number: ", err)
			ok = false
		}
	}
	if req.PostFormValue("mr[fee]") == "custom" {
		if fee < 50 && req.PostFormValue("mr[reduction]") != "requested" {
			data.FieldErr["customFee"] = "Für einen Betrag unter 50 CHF muss eine Ermässigung beantragt werden"
			numSubmitErrors.Add("low-fee-without-reduction", 1)
			ok = false
		} else if len(req.PostFormValue("mr[customFee]")) <= 0 {
			data.FieldErr["customFee"] = "Die Angabe eines Mitgliedsbeitrages ist notwendig"
			ok = false
		} else {
			var intfee uint64 = uint64(fee)
			data.MemberData.Fee = &intfee
		}
	} else if req.PostFormValue("mr[fee]") != "SFr. 50.--" {
		data.FieldErr["fee"] = "Unbekannter Wert für den Mitgliedsbeitrag"
		numSubmitErrors.Add("unknown-fee-value", 1)
		ok = false
	} else {
		var intfee uint64 = 50
		data.MemberData.Fee = &intfee
	}

	data.Comment = req.PostFormValue("mr[comments]")

	if ok {
		numSubmitted.Add(1)
		err = self.printTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing print template: ", err)
		}
	} else {
		err = self.applicationTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing request form template: ", err)
		}
	}
}

func init() {
	emailRe = regexp.MustCompile(`^[A-Za-z0-9-_\.]+@[A-Za-z0-9-_\.]+$`)
	phoneRe = regexp.MustCompile(`^\+?[0-9 -\.]+$`)
}
<commit_msg>Better error handling for templates. Initialize the MemberData member.<commit_after>/*
 * (c) 2013, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"expvar"
	"hash"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var fmap = template.FuncMap{
	"html": template.HTMLEscaper,
	"url":  UserInputFormatter,
}

func UserInputFormatter(v ...interface{}) string {
	return template.HTMLEscapeString(url.QueryEscape(v[0].(string)))
}

// Statistics.
var numRequests *expvar.Int = expvar.NewInt("num-http-requests")
var numSubmitted *expvar.Int = expvar.NewInt("num-successful-form-submissions")
var numSubmitErrors *expvar.Map = expvar.NewMap("num-form-submission-errors")

// Regular expressions for verification of the email and phone number fields.
var emailRe *regexp.Regexp
var phoneRe *regexp.Regexp

// Data type for the HTTP handler which takes the requests. We require the
// templates and a passthrough object for static content requests, so we
// need to hold some state.
type FormInputHandler struct {
	applicationTmpl *template.Template
	printTmpl       *template.Template
	passthrough     http.Handler
}

// Data used by the HTML template. Contains not just data entered so far,
// but also some error texts in case there was a problem submitting data.
type FormInputData struct {
	MemberData *Member
	Comment    string
	CommonErr  string
	FieldErr   map[string]string
}

// Parse the form data from the membership signup form and verify that it
// can be considered acceptable. If the data looks correct, return the
// print template for the user to sign and send in.
func (self *FormInputHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var err error
	var data FormInputData
	var fee float64
	var ok bool = true
	numRequests.Add(1)

	// Pass JavaScript and CSS requests through to the passthrough handler.
	if strings.HasPrefix(req.URL.Path, "/css/") ||
		strings.HasPrefix(req.URL.Path, "/js/") ||
		req.URL.Path == "/favicon.ico" {
		self.passthrough.ServeHTTP(w, req)
		return
	}

	data.FieldErr = make(map[string]string)
	data.MemberData = &Member{}

	if err = req.ParseForm(); err != nil {
		data.CommonErr = err.Error()
		numSubmitErrors.Add(err.Error(), 1)
		err = self.applicationTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing application template: ",
				err)
		}
		return
	}

	// No data entered: the user is probably just going to the web site
	// for the first time, so data validation is useless.
	if len(req.PostForm) == 0 {
		err = self.applicationTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing application template: ",
				err)
		}
		return
	}

	// You might think that it would be a good idea to split the name
	// field into first and last name, which might even work for this
	// specific, localized use case, but it is bad practice, because
	// some countries don't have the concept of last names, and would
	// set a bad precedent for people reading and using this code.
	var name string = req.PostFormValue("mr[name]")
	if len(name) <= 0 {
		numSubmitErrors.Add("no-name", 1)
		data.FieldErr["name"] = "Ein Name ist erforderlich"
	} else {
		data.MemberData.Name = &name
	}

	// The same applies to the address. There just is no globally common
	// format for home addresses, not everything has a house number, and
	// we don't want to encourage people to think so.
	var address string = req.PostFormValue("mr[address]")
	if len(address) <= 0 {
		numSubmitErrors.Add("no-street", 1)
		data.FieldErr["address"] = "Eine Adresse ist erforderlich"
	} else {
		data.MemberData.Street = &address
	}

	var city string = req.PostFormValue("mr[city]")
	if len(city) <= 0 {
		numSubmitErrors.Add("no-city", 1)
		data.FieldErr["city"] = "Ein Wohnort ist erforderlich"
	} else {
		data.MemberData.City = &city
	}

	// As for zip codes, you don't even have to go to India to find out
	// why enforcing a format there doesn't work. Many people around
	// here believe that a 4-5 digit number is enough to represent a zip
	// code. But then the Netherlands have zip codes like «4201 EB»
	// (where EB is part of the zip code). If you consider adding an
	// exception for this, please note that British zip codes look like
	// «G1 1PP». The only realistic way to deal with these is to allow
	// free text for zip codes.
	var zip string = req.PostFormValue("mr[zip]")
	if len(zip) <= 0 {
		numSubmitErrors.Add("no-zip", 1)
		data.FieldErr["zip"] = "Eine Postleitzahl ist erforderlich"
	} else {
		data.MemberData.Zipcode = &zip
	}

	// The country could arguably be a list.
	var country string = req.PostFormValue("mr[country]")
	if len(country) <= 0 {
		numSubmitErrors.Add("no-country", 1)
		data.FieldErr["country"] = "Ein Wohnland ist erforderlich"
	} else {
		data.MemberData.Country = &country
	}

	var email string = req.PostFormValue("mr[email]")
	if !emailRe.MatchString(email) {
		if len(email) > 0 {
			data.FieldErr["email"] = "Mailadresse sollte im Format a@b.ch sein"
			numSubmitErrors.Add("bad-email-format", 1)
		} else {
			data.FieldErr["email"] = "Muss angegeben werden"
			numSubmitErrors.Add("no-email", 1)
		}
		ok = false
	} else {
		data.MemberData.Email = &email
	}

	var phone string = req.PostFormValue("mr[telephone]")
	if len(phone) > 0 && !phoneRe.MatchString(phone) {
		data.FieldErr["telephone"] = "Telephonnummer sollte im Format +41 79 123 45 67 sein"
		numSubmitErrors.Add("bad-phone-format", 1)
		ok = false
	} else {
		data.MemberData.Phone = &phone
	}

	// TODO(caoimhe): Verify the user name field.
	var username string = req.PostFormValue("mr[username]")
	if len(username) > 0 {
		data.MemberData.Username = &username
	}

	var pw string = req.PostFormValue("mr[password]")
	if pw != req.PostFormValue("mr[passwordConfirm]") {
		data.FieldErr["password"] = "Passworte stimmen nicht überein"
		numSubmitErrors.Add("password-mismatch", 1)
		ok = false
	} else {
		var h hash.Hash = sha1.New()
		h.Write([]byte(pw))
		pw = "{SHA}" + base64.StdEncoding.EncodeToString(h.Sum([]byte{}))
		data.MemberData.Pwhash = &pw
	}

	if req.PostFormValue("mr[statutes]") != "accepted" {
		data.FieldErr["statutes"] = "Statuten müssen akzeptiert werden"
		numSubmitErrors.Add("statutes-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[ipay]") != "accepted" {
		data.FieldErr["ipay"] = "Zahlungsbereitschaft ist notwendig"
		numSubmitErrors.Add("payment-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[rules]") != "accepted" {
		data.FieldErr["rules"] = "Reglement muss akzeptiert werden"
		numSubmitErrors.Add("rules-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[gt18]") != "yes" {
		data.FieldErr["gt18"] = "Man muss mindestens 18 Jahre sein, um uns beizutreten"
		numSubmitErrors.Add("not-gt18", 1)
		ok = false
	}

	if len(req.PostFormValue("mr[customFee]")) > 0 {
		fee, err = strconv.ParseFloat(req.PostFormValue("mr[customFee]"), 64)
		if err == strconv.ErrRange {
			data.FieldErr["customFee"] = "Der Betrag ist irgendwie etwas gross/klein, oder?"
			numSubmitErrors.Add("fee-out-of-range", 1)
			ok = false
		} else if err == strconv.ErrSyntax {
			data.FieldErr["customFee"] = "Der Mitgliedsbeitrag kann nicht als Zahl identifiziert werden"
			numSubmitErrors.Add("fee-not-a-number", 1)
			log.Print("Unable to parse ", req.PostFormValue("mr[customFee]"),
				" as a valid fee")
			ok = false
		} else if err != nil {
			// No idea? This shouldn't really happen.
			data.FieldErr["customFee"] = err.Error()
			log.Print("Error converting ", req.PostFormValue("mr[customFee]"),
				" to a number: ", err)
			ok = false
		}
	}
	if req.PostFormValue("mr[fee]") == "custom" {
		if fee < 50 && req.PostFormValue("mr[reduction]") != "requested" {
			data.FieldErr["customFee"] = "Für einen Betrag unter 50 CHF muss eine Ermässigung beantragt werden"
			numSubmitErrors.Add("low-fee-without-reduction", 1)
			ok = false
		} else if len(req.PostFormValue("mr[customFee]")) <= 0 {
			data.FieldErr["customFee"] = "Die Angabe eines Mitgliedsbeitrages ist notwendig"
			ok = false
		} else {
			var intfee uint64 = uint64(fee)
			data.MemberData.Fee = &intfee
		}
	} else if req.PostFormValue("mr[fee]") != "SFr. 50.--" {
		data.FieldErr["fee"] = "Unbekannter Wert für den Mitgliedsbeitrag"
		numSubmitErrors.Add("unknown-fee-value", 1)
		ok = false
	} else {
		var intfee uint64 = 50
		data.MemberData.Fee = &intfee
	}

	data.Comment = req.PostFormValue("mr[comments]")

	if ok {
		numSubmitted.Add(1)
		err = self.printTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing print template: ", err)
		}
	} else {
		err = self.applicationTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing request form template: ",
				err)
		}
	}
}

func init() {
	emailRe = regexp.MustCompile(`^[A-Za-z0-9-_\.]+@[A-Za-z0-9-_\.]+$`)
	phoneRe = regexp.MustCompile(`^\+?[0-9 -\.]+$`)
}
<|endoftext|>


<commit_before>// meow_queue.go

package meow-data-structures

import "sync"

type meowQueueNode struct {
	meowData interface{}
	meowNxt *meowQueueNode
}

// go-routine safe FIFO
type meowQueue struct {
	meowHead *meowQueueNode
	meowTail *meowQueueNode
	meowCount int
	meowLock *sync.Mutex
}

// creating a new pointer to a new meowQueue
func meowNewQueue() *meowQueue {
	q := &meowQueue{}
	q.meowLock = &sync.Mutex{}
	return q
}

// go-routine safe...
func (q *meowQueue) meowLen() int {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	// returns number of elements in the meowQueue
	// ...in terms of size/length
	return q.meowCount
}

// meowQueue mutation
// go-routine safe
// meowPush <==> more like insertion thing
func (q *meowQueue) meowPush(stuffy interface{}) {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	// pushing/inserting a value at the meowTail (i.e. end) of the meowQueue
	ig := &meowQueueNode{meowData: stuffy}
	if q.meowTail == nil {
		q.meowTail = ig
		q.meowHead = ig
	} else {
		q.meowTail.meowNxt = ig
		q.meowTail = ig
	}
	q.meowCount++
}

// FIFO
// Queue mutation
func (q *meowQueue) meowDistro() interface() {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	if q.meowHead == nil {
		return nil
	}
	ig := q.meowHead
	q.meowHead = ig.meowNxt
	if q.meowHead == nil {
		q.meowTail = nil
	}
	q.meowCount--
	return ig.meowData
}<commit_msg>add meow_queue<commit_after>// meow_queue.go

package meow-data-structures

import "sync"

type meowQueueNode struct {
	meowData interface{}
	meowNxt *meowQueueNode
}

// go-routine safe FIFO
type meowQueue struct {
	meowHead *meowQueueNode
	meowTail *meowQueueNode
	meowCount int
	meowLock *sync.Mutex
}

// creating a new pointer to a new meowQueue
func meowNewQueue() *meowQueue {
	q := &meowQueue{}
	q.meowLock = &sync.Mutex{}
	return q
}

// go-routine safe...
func (q *meowQueue) meowLen() int {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	// returns number of elements in the meowQueue
	// ...in terms of size/length
	return q.meowCount
}

// meowQueue mutation
// go-routine safe
// meowPush <==> more like insertion thing
func (q *meowQueue) meowPush(stuffy interface{}) {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	// pushing/inserting a value at the meowTail (i.e. end) of the meowQueue
	ig := &meowQueueNode{meowData: stuffy}
	if q.meowTail == nil {
		q.meowTail = ig
		q.meowHead = ig
	} else {
		q.meowTail.meowNxt = ig
		q.meowTail = ig
	}
	q.meowCount++
}

// FIFO <==> returns
// Queue mutation
func (q *meowQueue) meowDistro() interface() {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	if q.meowHead == nil {
		return nil
	}
	ig := q.meowHead
	q.meowHead = ig.meowNxt
	if q.meowHead == nil {
		q.meowTail = nil
	}
	q.meowCount--
	return ig.meowData
}

// No meowQueue mutation
// FIFO <==> reads
func (q *meowQueue) meowErect() interface{} {
	q.meowLock.Lock()
	defer q.meowLock.Unlock()
	ig := q.meowHead
	if ig == nil || ig.meowData == nil {
		return nil
	}
	return ig.meowData
}<|endoftext|>