<commit_before>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package cni

import (
	"context"
	"fmt"
	"os"
	"strings"
	"sync"

	cnilibrary "github.com/containernetworking/cni/libcni"
	"github.com/containernetworking/cni/pkg/invoke"
	"github.com/containernetworking/cni/pkg/types"
	types100 "github.com/containernetworking/cni/pkg/types/100"
	"github.com/containernetworking/cni/pkg/version"
)

type CNI interface {
	// Setup setup the network for the namespace
	Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error)
	// Remove tears down the network of the namespace.
	Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
	// Check checks if the network is still in desired state
	Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
	// Load loads the cni network config
	Load(opts ...Opt) error
	// Status checks the status of the cni initialization
	Status() error
	// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
	GetConfig() *ConfigResult
}

type ConfigResult struct {
	PluginDirs       []string
	PluginConfDir    string
	PluginMaxConfNum int
	Prefix           string
	Networks         []*ConfNetwork
}

type ConfNetwork struct {
	Config *NetworkConfList
	IFName string
}

// NetworkConfList is a source bytes to string version of cnilibrary.NetworkConfigList
type NetworkConfList struct {
	Name       string
	CNIVersion string
	Plugins    []*NetworkConf
	Source     string
}

// NetworkConf is a source bytes to string conversion of cnilibrary.NetworkConfig
type NetworkConf struct {
	Network *types.NetConf
	Source  string
}

type libcni struct {
	config

	cniConfig    cnilibrary.CNI
	networkCount int // minimum network plugin configurations needed to initialize cni
	networks     []*Network
	sync.RWMutex
}

func defaultCNIConfig() *libcni {
	return &libcni{
		config: config{
			pluginDirs:       []string{DefaultCNIDir},
			pluginConfDir:    DefaultNetDir,
			pluginMaxConfNum: DefaultMaxConfNum,
			prefix:           DefaultPrefix,
		},
		cniConfig: cnilibrary.NewCNIConfig(
			[]string{
				DefaultCNIDir,
			},
			&invoke.DefaultExec{
				RawExec:       &invoke.RawExec{Stderr: os.Stderr},
				PluginDecoder: version.PluginDecoder{},
			},
		),
		networkCount: 1,
	}
}

// New creates a new libcni instance.
func New(config ...Opt) (CNI, error) {
	cni := defaultCNIConfig()
	var err error
	for _, c := range config {
		if err = c(cni); err != nil {
			return nil, err
		}
	}
	return cni, nil
}

// Load loads the latest config from cni config files.
func (c *libcni) Load(opts ...Opt) error {
	var err error
	c.Lock()
	defer c.Unlock()
	// Reset the networks on a load operation to ensure
	// config happens on a clean slate
	c.reset()

	for _, o := range opts {
		if err = o(c); err != nil {
			return fmt.Errorf("cni config load failed: %v: %w", err, ErrLoad)
		}
	}
	return nil
}

// Status returns the status of CNI initialization.
func (c *libcni) Status() error {
	c.RLock()
	defer c.RUnlock()
	if len(c.networks) < c.networkCount {
		return ErrCNINotInitialized
	}
	return nil
}

// Networks returns all the configured networks.
// NOTE: Caller MUST NOT modify anything in the returned array.
func (c *libcni) Networks() []*Network {
	c.RLock()
	defer c.RUnlock()
	return append([]*Network{}, c.networks...)
}

// Setup setups the network in the namespace and returns a Result
func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
	if err := c.Status(); err != nil {
		return nil, err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return nil, err
	}
	result, err := c.attachNetworks(ctx, ns)
	if err != nil {
		return nil, err
	}
	return c.createResult(result)
}

type asynchAttachResult struct {
	index int
	res   *types100.Result
	err   error
}

func asynchAttach(ctx context.Context, index int, n *Network, ns *Namespace, wg *sync.WaitGroup, rc chan asynchAttachResult) {
	defer wg.Done()
	r, err := n.Attach(ctx, ns)
	rc <- asynchAttachResult{index: index, res: r, err: err}
}

func (c *libcni) attachNetworks(ctx context.Context, ns *Namespace) ([]*types100.Result, error) {
	var wg sync.WaitGroup
	var firstError error
	results := make([]*types100.Result, len(c.Networks()))
	rc := make(chan asynchAttachResult)

	for i, network := range c.Networks() {
		wg.Add(1)
		go asynchAttach(ctx, i, network, ns, &wg, rc)
	}

	for range c.Networks() {
		rs := <-rc
		if rs.err != nil && firstError == nil {
			firstError = rs.err
		}
		results[rs.index] = rs.res
	}
	wg.Wait()

	return results, firstError
}

// Remove removes the network config from the namespace
func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
	if err := c.Status(); err != nil {
		return err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return err
	}
	for _, network := range c.Networks() {
		if err := network.Remove(ctx, ns); err != nil {
			// Based on CNI spec v0.7.0, empty network namespace is allowed to
			// do best effort cleanup. However, it is not handled consistently
			// right now:
			// https://github.com/containernetworking/plugins/issues/210
			// TODO(random-liu): Remove the error handling when the issue is
			// fixed and the CNI spec v0.6.0 support is deprecated.
			// NOTE(claudiub): Some CNIs could return a "not found" error, which could mean that
			// it was already deleted.
			if (path == "" && strings.Contains(err.Error(), "no such file or directory")) || strings.Contains(err.Error(), "not found") {
				continue
			}
			return err
		}
	}
	return nil
}

// Check checks if the network is still in desired state
func (c *libcni) Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
	if err := c.Status(); err != nil {
		return err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return err
	}
	for _, network := range c.Networks() {
		err := network.Check(ctx, ns)
		if err != nil {
			return err
		}
	}

	return nil
}

// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
func (c *libcni) GetConfig() *ConfigResult {
	c.RLock()
	defer c.RUnlock()
	r := &ConfigResult{
		PluginDirs:       c.config.pluginDirs,
		PluginConfDir:    c.config.pluginConfDir,
		PluginMaxConfNum: c.config.pluginMaxConfNum,
		Prefix:           c.config.prefix,
	}
	for _, network := range c.networks {
		conf := &NetworkConfList{
			Name:       network.config.Name,
			CNIVersion: network.config.CNIVersion,
			Source:     string(network.config.Bytes),
		}
		for _, plugin := range network.config.Plugins {
			conf.Plugins = append(conf.Plugins, &NetworkConf{
				Network: plugin.Network,
				Source:  string(plugin.Bytes),
			})
		}
		r.Networks = append(r.Networks, &ConfNetwork{
			Config: conf,
			IFName: network.ifName,
		})
	}
	return r
}

func (c *libcni) reset() {
	c.networks = nil
}
<commit_msg>Re-introduce serial network setup<commit_after>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package cni

import (
	"context"
	"fmt"
	"os"
	"strings"
	"sync"

	cnilibrary "github.com/containernetworking/cni/libcni"
	"github.com/containernetworking/cni/pkg/invoke"
	"github.com/containernetworking/cni/pkg/types"
	types100 "github.com/containernetworking/cni/pkg/types/100"
	"github.com/containernetworking/cni/pkg/version"
)

type CNI interface {
	// Setup setup the network for the namespace
	Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error)
	// SetupSerially sets up each of the network interfaces for the namespace in serial
	SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error)
	// Remove tears down the network of the namespace.
	Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
	// Check checks if the network is still in desired state
	Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
	// Load loads the cni network config
	Load(opts ...Opt) error
	// Status checks the status of the cni initialization
	Status() error
	// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
	GetConfig() *ConfigResult
}

type ConfigResult struct {
	PluginDirs       []string
	PluginConfDir    string
	PluginMaxConfNum int
	Prefix           string
	Networks         []*ConfNetwork
}

type ConfNetwork struct {
	Config *NetworkConfList
	IFName string
}

// NetworkConfList is a source bytes to string version of cnilibrary.NetworkConfigList
type NetworkConfList struct {
	Name       string
	CNIVersion string
	Plugins    []*NetworkConf
	Source     string
}

// NetworkConf is a source bytes to string conversion of cnilibrary.NetworkConfig
type NetworkConf struct {
	Network *types.NetConf
	Source  string
}

type libcni struct {
	config

	cniConfig    cnilibrary.CNI
	networkCount int // minimum network plugin configurations needed to initialize cni
	networks     []*Network
	sync.RWMutex
}

func defaultCNIConfig() *libcni {
	return &libcni{
		config: config{
			pluginDirs:       []string{DefaultCNIDir},
			pluginConfDir:    DefaultNetDir,
			pluginMaxConfNum: DefaultMaxConfNum,
			prefix:           DefaultPrefix,
		},
		cniConfig: cnilibrary.NewCNIConfig(
			[]string{
				DefaultCNIDir,
			},
			&invoke.DefaultExec{
				RawExec:       &invoke.RawExec{Stderr: os.Stderr},
				PluginDecoder: version.PluginDecoder{},
			},
		),
		networkCount: 1,
	}
}

// New creates a new libcni instance.
func New(config ...Opt) (CNI, error) {
	cni := defaultCNIConfig()
	var err error
	for _, c := range config {
		if err = c(cni); err != nil {
			return nil, err
		}
	}
	return cni, nil
}

// Load loads the latest config from cni config files.
func (c *libcni) Load(opts ...Opt) error {
	var err error
	c.Lock()
	defer c.Unlock()
	// Reset the networks on a load operation to ensure
	// config happens on a clean slate
	c.reset()

	for _, o := range opts {
		if err = o(c); err != nil {
			return fmt.Errorf("cni config load failed: %v: %w", err, ErrLoad)
		}
	}
	return nil
}

// Status returns the status of CNI initialization.
func (c *libcni) Status() error {
	c.RLock()
	defer c.RUnlock()
	if len(c.networks) < c.networkCount {
		return ErrCNINotInitialized
	}
	return nil
}

// Networks returns all the configured networks.
// NOTE: Caller MUST NOT modify anything in the returned array.
func (c *libcni) Networks() []*Network {
	c.RLock()
	defer c.RUnlock()
	return append([]*Network{}, c.networks...)
}

// Setup setups the network in the namespace and returns a Result
func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
	if err := c.Status(); err != nil {
		return nil, err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return nil, err
	}
	result, err := c.attachNetworks(ctx, ns)
	if err != nil {
		return nil, err
	}
	return c.createResult(result)
}

// SetupSerially setups the network in the namespace and returns a Result
func (c *libcni) SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
	if err := c.Status(); err != nil {
		return nil, err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return nil, err
	}
	result, err := c.attachNetworksSerially(ctx, ns)
	if err != nil {
		return nil, err
	}
	return c.createResult(result)
}

func (c *libcni) attachNetworksSerially(ctx context.Context, ns *Namespace) ([]*types100.Result, error) {
	var results []*types100.Result
	for _, network := range c.Networks() {
		r, err := network.Attach(ctx, ns)
		if err != nil {
			return nil, err
		}
		results = append(results, r)
	}
	return results, nil
}

type asynchAttachResult struct {
	index int
	res   *types100.Result
	err   error
}

func asynchAttach(ctx context.Context, index int, n *Network, ns *Namespace, wg *sync.WaitGroup, rc chan asynchAttachResult) {
	defer wg.Done()
	r, err := n.Attach(ctx, ns)
	rc <- asynchAttachResult{index: index, res: r, err: err}
}

func (c *libcni) attachNetworks(ctx context.Context, ns *Namespace) ([]*types100.Result, error) {
	var wg sync.WaitGroup
	var firstError error
	results := make([]*types100.Result, len(c.Networks()))
	rc := make(chan asynchAttachResult)

	for i, network := range c.Networks() {
		wg.Add(1)
		go asynchAttach(ctx, i, network, ns, &wg, rc)
	}

	for range c.Networks() {
		rs := <-rc
		if rs.err != nil && firstError == nil {
			firstError = rs.err
		}
		results[rs.index] = rs.res
	}
	wg.Wait()

	return results, firstError
}

// Remove removes the network config from the namespace
func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
	if err := c.Status(); err != nil {
		return err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return err
	}
	for _, network := range c.Networks() {
		if err := network.Remove(ctx, ns); err != nil {
			// Based on CNI spec v0.7.0, empty network namespace is allowed to
			// do best effort cleanup. However, it is not handled consistently
			// right now:
			// https://github.com/containernetworking/plugins/issues/210
			// TODO(random-liu): Remove the error handling when the issue is
			// fixed and the CNI spec v0.6.0 support is deprecated.
			// NOTE(claudiub): Some CNIs could return a "not found" error, which could mean that
			// it was already deleted.
			if (path == "" && strings.Contains(err.Error(), "no such file or directory")) || strings.Contains(err.Error(), "not found") {
				continue
			}
			return err
		}
	}
	return nil
}

// Check checks if the network is still in desired state
func (c *libcni) Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
	if err := c.Status(); err != nil {
		return err
	}
	ns, err := newNamespace(id, path, opts...)
	if err != nil {
		return err
	}
	for _, network := range c.Networks() {
		err := network.Check(ctx, ns)
		if err != nil {
			return err
		}
	}

	return nil
}

// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
func (c *libcni) GetConfig() *ConfigResult {
	c.RLock()
	defer c.RUnlock()
	r := &ConfigResult{
		PluginDirs:       c.config.pluginDirs,
		PluginConfDir:    c.config.pluginConfDir,
		PluginMaxConfNum: c.config.pluginMaxConfNum,
		Prefix:           c.config.prefix,
	}
	for _, network := range c.networks {
		conf := &NetworkConfList{
			Name:       network.config.Name,
			CNIVersion: network.config.CNIVersion,
			Source:     string(network.config.Bytes),
		}
		for _, plugin := range network.config.Plugins {
			conf.Plugins = append(conf.Plugins, &NetworkConf{
				Network: plugin.Network,
				Source:  string(plugin.Bytes),
			})
		}
		r.Networks = append(r.Networks, &ConfNetwork{
			Config: conf,
			IFName: network.ifName,
		})
	}
	return r
}

func (c *libcni) reset() {
	c.networks = nil
}
<|endoftext|>


<commit_before>package sysinfo

import (
	"bufio"
	"os"
	"strconv"
	"strings"
)

const (
	cpuInfoPath    = "/proc/cpuinfo"
	cpuMinFreqPath = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq"
	cpuMaxFreqPath = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
)

type CPU struct {
	Name        string
	Model       uint64
	Family      uint64
	VendorID    string
	Stepping    string
	Cache       uint64
	Flags       []string
	MinFreq     uint64
	MaxFreq     uint64
	CoreCount   uint64
	ThreadCount uint64
	SocketCount uint64
}

func CPUInfo() (*CPU, error) {
	var cpu = &CPU{}

	// read cpu max frequency
	minFreq, err := readSingleValueFile(cpuMinFreqPath)
	if err != nil {
		return nil, err
	}

	cpu.MinFreq, err = strconv.ParseUint(minFreq, 10, 64)
	if err != nil {
		return nil, err
	}

	// read cpu min frequency
	maxFreq, err := readSingleValueFile(cpuMaxFreqPath)
	if err != nil {
		return nil, err
	}

	cpu.MaxFreq, err = strconv.ParseUint(maxFreq, 10, 64)
	if err != nil {
		return nil, err
	}

	// parse cpuinfo file
	file, err := os.Open(cpuInfoPath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	cores := map[uint64]struct{}{}
	sockets := map[uint64]struct{}{}

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		fields := strings.Split(line, ":")
		if len(fields) != 2 {
			return nil, ErrInvalidFileFormat
		}

		value := strings.TrimSpace(fields[1])
		key := strings.ToLower(strings.TrimSpace(fields[0]))
		switch key {
		case "processor":
			cpu.ThreadCount++

		case "model name":
			if cpu.Name == "" {
				cpu.Name = value
			}

		case "model":
			if cpu.Model != 0 {
				continue
			}

			cpu.Model, err = strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

		case "cpu family":
			if cpu.Family != 0 {
				continue
			}

			cpu.Family, err = strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

		case "vendor_id":
			if cpu.VendorID == "" {
				cpu.VendorID = value
			}

		case "stepping":
			if cpu.Stepping == "" {
				cpu.Stepping = value
			}

		case "cache size":
			if cpu.Cache != 0 {
				continue
			}

			cacheFields := strings.Fields(value)
			if len(cacheFields) != 2 {
				return nil, ErrInvalidFileFormat
			}

			cpu.Cache, err = strconv.ParseUint(cacheFields[0], 10, 64)
			if err != nil {
				return nil, err
			}

		case "physical id":
			socket, err := strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

			if _, ok := sockets[socket]; !ok {
				sockets[socket] = struct{}{}
			}

		case "core id":
			core, err := strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

			if _, ok := cores[core]; !ok {
				cores[core] = struct{}{}
			}

		case "flags":
			if len(cpu.Flags) > 0 {
				continue
			}

			cpu.Flags = strings.Fields(value)
		}
	}

	if err = scanner.Err(); err != nil {
		return nil, err
	}

	cpu.CoreCount = uint64(len(cores))
	cpu.SocketCount = uint64(len(sockets))

	return cpu, nil
}
<commit_msg>Add CPU usage<commit_after>package sysinfo

import (
	"bufio"
	"os"
	"strconv"
	"strings"
)

const (
	cpuStatPath    = "/proc/stat"
	cpuInfoPath    = "/proc/cpuinfo"
	cpuMinFreqPath = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq"
	cpuMaxFreqPath = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
)

type CPU struct {
	Name        string
	Model       uint64
	Family      uint64
	VendorID    string
	Stepping    string
	Cache       uint64
	Flags       []string
	MinFreq     uint64
	MaxFreq     uint64
	CoreCount   uint64
	ThreadCount uint64
	SocketCount uint64
}

type CPUUsage struct {
	Total     uint64
	User      uint64
	Nice      uint64
	System    uint64
	Idle      uint64
	IOWait    uint64
	IRQ       uint64
	SoftIRQ   uint64
	Steal     uint64
	Guest     uint64
	GuestNice uint64
}

func CPUInfo() (*CPU, error) {
	var cpu = &CPU{}

	// read cpu max frequency
	minFreq, err := readSingleValueFile(cpuMinFreqPath)
	if err != nil {
		return nil, err
	}

	cpu.MinFreq, err = strconv.ParseUint(minFreq, 10, 64)
	if err != nil {
		return nil, err
	}

	// read cpu min frequency
	maxFreq, err := readSingleValueFile(cpuMaxFreqPath)
	if err != nil {
		return nil, err
	}

	cpu.MaxFreq, err = strconv.ParseUint(maxFreq, 10, 64)
	if err != nil {
		return nil, err
	}

	// parse cpuinfo file
	file, err := os.Open(cpuInfoPath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	cores := map[uint64]struct{}{}
	sockets := map[uint64]struct{}{}

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		fields := strings.Split(line, ":")
		if len(fields) != 2 {
			return nil, ErrInvalidFileFormat
		}

		value := strings.TrimSpace(fields[1])
		key := strings.ToLower(strings.TrimSpace(fields[0]))
		switch key {
		case "processor":
			cpu.ThreadCount++

		case "model name":
			if cpu.Name == "" {
				cpu.Name = value
			}

		case "model":
			if cpu.Model != 0 {
				continue
			}

			cpu.Model, err = strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

		case "cpu family":
			if cpu.Family != 0 {
				continue
			}

			cpu.Family, err = strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

		case "vendor_id":
			if cpu.VendorID == "" {
				cpu.VendorID = value
			}

		case "stepping":
			if cpu.Stepping == "" {
				cpu.Stepping = value
			}

		case "cache size":
			if cpu.Cache != 0 {
				continue
			}

			cacheFields := strings.Fields(value)
			if len(cacheFields) != 2 {
				return nil, ErrInvalidFileFormat
			}

			cpu.Cache, err = strconv.ParseUint(cacheFields[0], 10, 64)
			if err != nil {
				return nil, err
			}

		case "physical id":
			socket, err := strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

			if _, ok := sockets[socket]; !ok {
				sockets[socket] = struct{}{}
			}

		case "core id":
			core, err := strconv.ParseUint(value, 10, 64)
			if err != nil {
				return nil, err
			}

			if _, ok := cores[core]; !ok {
				cores[core] = struct{}{}
			}

		case "flags":
			if len(cpu.Flags) > 0 {
				continue
			}

			cpu.Flags = strings.Fields(value)
		}
	}

	if err = scanner.Err(); err != nil {
		return nil, err
	}

	cpu.CoreCount = uint64(len(cores))
	cpu.SocketCount = uint64(len(sockets))

	return cpu, nil
}

func CPUUsageInfo() (*CPUUsage, []*CPUUsage, error) {
	file, err := os.Open(cpuStatPath)
	if err != nil {
		return nil, nil, err
	}
	defer file.Close()

	var globalUsage *CPUUsage
	var usagePerCore []*CPUUsage

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if !strings.HasPrefix(line, "cpu") {
			continue
		}

		fields := strings.Fields(line)
		if len(fields) != 11 {
			return nil, nil, err
		}

		usage := &CPUUsage{}

		usage.User, err = strconv.ParseUint(fields[1], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.Nice, err = strconv.ParseUint(fields[2], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.System, err = strconv.ParseUint(fields[3], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.Idle, err = strconv.ParseUint(fields[4], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.IOWait, err = strconv.ParseUint(fields[5], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.IRQ, err = strconv.ParseUint(fields[6], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.SoftIRQ, err = strconv.ParseUint(fields[7], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.Steal, err = strconv.ParseUint(fields[8], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.Guest, err = strconv.ParseUint(fields[9], 10, 64)
		if err != nil {
			return nil, nil, err
		}
		usage.GuestNice, err = strconv.ParseUint(fields[10], 10, 64)
		if err != nil {
			return nil, nil, err
		}

		usage.User -= usage.Guest
		usage.Nice -= usage.GuestNice
		usage.Total = usage.User + usage.Nice + usage.System + usage.Idle +
			usage.IOWait + usage.IRQ + usage.SoftIRQ + usage.Steal +
			usage.Guest + usage.GuestNice

		if fields[0] == "cpu" {
			globalUsage = usage
			continue
		}

		usagePerCore = append(usagePerCore, usage)
	}

	if globalUsage == nil || len(usagePerCore) == 0 {
		return nil, nil, ErrInvalidFileFormat
	}
	if err = scanner.Err(); err != nil {
		return nil, nil, err
	}

	return globalUsage, usagePerCore, nil
}

func PercentCPUUsed(firstSample *CPUUsage, secondSample *CPUUsage) float64 {
	if firstSample == nil || secondSample == nil {
		return 0.0
	}
	if firstSample.Total == 0 && secondSample.Total == 0 {
		return 0.0
	}

	if firstSample.Total > secondSample.Total {
		firstSample, secondSample = secondSample, firstSample
	}

	deltaTotal := secondSample.Total - firstSample.Total
	if deltaTotal == 0 {
		deltaTotal = secondSample.Total
	}

	deltaIdle := (secondSample.Idle + secondSample.IOWait) -
		(firstSample.Idle + firstSample.IOWait)
	if deltaIdle == 0 {
		deltaIdle = secondSample.Idle
	}

	return float64(deltaTotal-deltaIdle) / float64(deltaTotal) * 100.0
}
<|endoftext|>


<commit_before>// Copyright 2017-present The Hugo 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 release implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

const (
	issueLinkTemplate            = "[#%d](https://github.com/gohugoio/hugo/issues/%d)"
	linkTemplate                 = "[%s](%s)"
	releaseNotesMarkdownTemplate = `
{{- $patchRelease := isPatch . -}}
{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}

{{- if $patchRelease }}
{{ if eq (len .All) 1 }}
This is a bug-fix release with one important fix.
{{ else }}
This is a bug-fix release with a couple of important fixes.
{{ end }}
{{ else }}
This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
{{ end -}}

{{- if  gt (len $contribsPerAuthor) 3 -}}
{{- $u1 := index $contribsPerAuthor 0 -}}
{{- $u2 := index $contribsPerAuthor 1 -}}
{{- $u3 := index $contribsPerAuthor 2 -}}
{{- $u4 := index $contribsPerAuthor 3 -}}
{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions.
And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
{{ end }}
Hugo now has:

{{ with .Repo -}}
* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers)
* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
{{- end -}}
{{ with .ThemeCount }}
* 156+ [themes](http://themes.gohugo.io/)
{{- end }}
{{ with .Notes }}
## Notes
{{ template "change-section" . }}
{{- end -}}
## Enhancements
{{ template "change-headers"  .Enhancements -}}
## Fixes
{{ template "change-headers"  .Fixes -}}

{{ define "change-headers" }}
{{ $tmplChanges := index . "templateChanges" -}}
{{- $outChanges := index . "outChanges" -}}
{{- $coreChanges := index . "coreChanges" -}}
{{- $docsChanges := index . "docsChanges" -}}
{{- $otherChanges := index . "otherChanges" -}}
{{- with $tmplChanges -}}
### Templates
{{ template "change-section" . }}
{{- end -}}
{{- with $outChanges -}}
### Output
{{ template "change-section"  . }}
{{- end -}}
{{- with $coreChanges -}}
### Core
{{ template "change-section" . }}
{{- end -}}
{{- with $docsChanges -}}
### Docs
{{ template "change-section"  . }}
{{- end -}}
{{- with $otherChanges -}}
### Other
{{ template "change-section"  . }}
{{- end -}}
{{ end }}

{{ define "change-section" }}
{{ range . }}
{{- if .GitHubCommit -}}
* {{ .Subject }} {{ . | commitURL }} {{ . | authorURL }} {{ range .Issues }}{{ . | issue }} {{ end }}
{{ else -}}
* {{ .Subject }} {{ range .Issues }}{{ . | issue }} {{ end }}
{{ end -}}
{{- end }}
{{ end }}
`
)

var templateFuncs = template.FuncMap{
	"isPatch": func(c changeLog) bool {
		return strings.Count(c.Version, ".") > 1
	},
	"issue": func(id int) string {
		return fmt.Sprintf(issueLinkTemplate, id, id)
	},
	"commitURL": func(info gitInfo) string {
		if info.GitHubCommit.HtmlURL == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HtmlURL)
	},
	"authorURL": func(info gitInfo) string {
		if info.GitHubCommit.Author.Login == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HtmlURL)
	},
}

func writeReleaseNotes(version string, infos gitInfos, to io.Writer) error {
	changes := gitInfosToChangeLog(infos)
	changes.Version = version
	repo, err := fetchRepo()
	if err == nil {
		changes.Repo = &repo
	}
	themeCount, err := fetchThemeCount()
	if err == nil {
		changes.ThemeCount = themeCount
	}

	tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate)
	if err != nil {
		return err
	}

	err = tmpl.Execute(to, changes)
	if err != nil {
		return err
	}

	return nil

}

func fetchThemeCount() (int, error) {
	resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemes/master/.gitmodules")
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	b, _ := ioutil.ReadAll(resp.Body)
	return bytes.Count(b, []byte("submodule")), nil
}

func writeReleaseNotesToTmpFile(version string, infos gitInfos) (string, error) {
	f, err := ioutil.TempFile("", "hugorelease")
	if err != nil {
		return "", err
	}

	defer f.Close()

	if err := writeReleaseNotes(version, infos, f); err != nil {
		return "", err
	}

	return f.Name(), nil
}

func getReleaseNotesDocsTempDirAndName(version string) (string, string) {
	return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes.md", version)
}

func getReleaseNotesDocsTempFilename(version string) string {
	return filepath.Join(getReleaseNotesDocsTempDirAndName(version))
}

func writeReleaseNotesToTemp(version string, infos gitInfos) (string, error) {
	docsTempPath, name := getReleaseNotesDocsTempDirAndName(version)
	os.Mkdir(docsTempPath, os.ModePerm)

	f, err := os.Create(filepath.Join(docsTempPath, name))
	if err != nil {
		return "", err
	}

	defer f.Close()

	if err := writeReleaseNotes(version, infos, f); err != nil {
		return "", err
	}

	return f.Name(), nil

}

func writeReleaseNotesToDocs(title, sourceFilename string) (string, error) {
	targetFilename := filepath.Base(sourceFilename)
	contentDir := hugoFilepath("docs/content/release-notes")
	targetFullFilename := filepath.Join(contentDir, targetFilename)
	os.Mkdir(contentDir, os.ModePerm)

	b, err := ioutil.ReadFile(sourceFilename)
	if err != nil {
		return "", err
	}

	f, err := os.Create(targetFullFilename)
	if err != nil {
		return "", err
	}
	defer f.Close()

	if _, err := f.WriteString(fmt.Sprintf(`
---
date: %s
title: %s
---

	`, time.Now().Format("2006-01-02"), title)); err != nil {
		return "", err
	}

	if _, err := f.Write(b); err != nil {
		return "", err
	}

	return targetFullFilename, nil

}
<commit_msg>releaser: Remove trailing space in fixes list<commit_after>// Copyright 2017-present The Hugo 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 release implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
)

const (
	issueLinkTemplate            = "[#%d](https://github.com/gohugoio/hugo/issues/%d)"
	linkTemplate                 = "[%s](%s)"
	releaseNotesMarkdownTemplate = `
{{- $patchRelease := isPatch . -}}
{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}}

{{- if $patchRelease }}
{{ if eq (len .All) 1 }}
This is a bug-fix release with one important fix.
{{ else }}
This is a bug-fix release with a couple of important fixes.
{{ end }}
{{ else }}
This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base.
{{ end -}}

{{- if  gt (len $contribsPerAuthor) 3 -}}
{{- $u1 := index $contribsPerAuthor 0 -}}
{{- $u2 := index $contribsPerAuthor 1 -}}
{{- $u3 := index $contribsPerAuthor 2 -}}
{{- $u4 := index $contribsPerAuthor 3 -}}
{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions.
And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition.
{{ end }}
Hugo now has:

{{ with .Repo -}}
* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers)
* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors)
{{- end -}}
{{ with .ThemeCount }}
* 156+ [themes](http://themes.gohugo.io/)
{{- end }}
{{ with .Notes }}
## Notes
{{ template "change-section" . }}
{{- end -}}
## Enhancements
{{ template "change-headers"  .Enhancements -}}
## Fixes
{{ template "change-headers"  .Fixes -}}

{{ define "change-headers" }}
{{ $tmplChanges := index . "templateChanges" -}}
{{- $outChanges := index . "outChanges" -}}
{{- $coreChanges := index . "coreChanges" -}}
{{- $docsChanges := index . "docsChanges" -}}
{{- $otherChanges := index . "otherChanges" -}}
{{- with $tmplChanges -}}
### Templates
{{ template "change-section" . }}
{{- end -}}
{{- with $outChanges -}}
### Output
{{ template "change-section"  . }}
{{- end -}}
{{- with $coreChanges -}}
### Core
{{ template "change-section" . }}
{{- end -}}
{{- with $docsChanges -}}
### Docs
{{ template "change-section"  . }}
{{- end -}}
{{- with $otherChanges -}}
### Other
{{ template "change-section"  . }}
{{- end -}}
{{ end }}

{{ define "change-section" }}
{{ range . }}
{{- if .GitHubCommit -}}
* {{ .Subject }} {{ . | commitURL }} {{ . | authorURL }} {{ range .Issues }}{{ . | issue }}{{ end }}
{{ else -}}
* {{ .Subject }} {{ range .Issues }}{{ . | issue }}{{ end }}
{{ end -}}
{{- end }}
{{ end }}
`
)

var templateFuncs = template.FuncMap{
	"isPatch": func(c changeLog) bool {
		return strings.Count(c.Version, ".") > 1
	},
	"issue": func(id int) string {
		return fmt.Sprintf(issueLinkTemplate, id, id)
	},
	"commitURL": func(info gitInfo) string {
		if info.GitHubCommit.HtmlURL == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HtmlURL)
	},
	"authorURL": func(info gitInfo) string {
		if info.GitHubCommit.Author.Login == "" {
			return ""
		}
		return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HtmlURL)
	},
}

func writeReleaseNotes(version string, infos gitInfos, to io.Writer) error {
	changes := gitInfosToChangeLog(infos)
	changes.Version = version
	repo, err := fetchRepo()
	if err == nil {
		changes.Repo = &repo
	}
	themeCount, err := fetchThemeCount()
	if err == nil {
		changes.ThemeCount = themeCount
	}

	tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate)
	if err != nil {
		return err
	}

	err = tmpl.Execute(to, changes)
	if err != nil {
		return err
	}

	return nil

}

func fetchThemeCount() (int, error) {
	resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemes/master/.gitmodules")
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	b, _ := ioutil.ReadAll(resp.Body)
	return bytes.Count(b, []byte("submodule")), nil
}

func writeReleaseNotesToTmpFile(version string, infos gitInfos) (string, error) {
	f, err := ioutil.TempFile("", "hugorelease")
	if err != nil {
		return "", err
	}

	defer f.Close()

	if err := writeReleaseNotes(version, infos, f); err != nil {
		return "", err
	}

	return f.Name(), nil
}

func getReleaseNotesDocsTempDirAndName(version string) (string, string) {
	return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes.md", version)
}

func getReleaseNotesDocsTempFilename(version string) string {
	return filepath.Join(getReleaseNotesDocsTempDirAndName(version))
}

func writeReleaseNotesToTemp(version string, infos gitInfos) (string, error) {
	docsTempPath, name := getReleaseNotesDocsTempDirAndName(version)
	os.Mkdir(docsTempPath, os.ModePerm)

	f, err := os.Create(filepath.Join(docsTempPath, name))
	if err != nil {
		return "", err
	}

	defer f.Close()

	if err := writeReleaseNotes(version, infos, f); err != nil {
		return "", err
	}

	return f.Name(), nil

}

func writeReleaseNotesToDocs(title, sourceFilename string) (string, error) {
	targetFilename := filepath.Base(sourceFilename)
	contentDir := hugoFilepath("docs/content/release-notes")
	targetFullFilename := filepath.Join(contentDir, targetFilename)
	os.Mkdir(contentDir, os.ModePerm)

	b, err := ioutil.ReadFile(sourceFilename)
	if err != nil {
		return "", err
	}

	f, err := os.Create(targetFullFilename)
	if err != nil {
		return "", err
	}
	defer f.Close()

	if _, err := f.WriteString(fmt.Sprintf(`
---
date: %s
title: %s
---

	`, time.Now().Format("2006-01-02"), title)); err != nil {
		return "", err
	}

	if _, err := f.Write(b); err != nil {
		return "", err
	}

	return targetFullFilename, nil

}
<|endoftext|>


<commit_before>package toot

type Account struct {
	Id             string `json:"id"`
	Username       string `json:"username"`
	Acct           string `json:"acct"`
	DisplayName    string `json:"display_name"`
	Note           string `json:"note"`
	Url            string `json:"url"`
	Avatar         string `json:"avatar"`
	Header         string `json:"header"`
	Locked         bool   `json:"locked"`
	CreatedAt      string `json:"created_at"`
	FollowersCount int    `json:"followers_count"`
	FollowingCount int    `json:"following_count"`
	StatusesCount  int    `json:"statuses_count"`
}
<commit_msg>Add scope<commit_after>package toot

import (
	"strings"
)

type Account struct {
	Id             string `json:"id"`
	Username       string `json:"username"`
	Acct           string `json:"acct"`
	DisplayName    string `json:"display_name"`
	Note           string `json:"note"`
	Url            string `json:"url"`
	Avatar         string `json:"avatar"`
	Header         string `json:"header"`
	Locked         bool   `json:"locked"`
	CreatedAt      string `json:"created_at"`
	FollowersCount int    `json:"followers_count"`
	FollowingCount int    `json:"following_count"`
	StatusesCount  int    `json:"statuses_count"`
}

type Scope struct {
	Read   bool
	Write  bool
	Follow bool
}

func (s Scope) String() string {
	scopes := []string{}

	if s.Read {
		scopes = append(scopes, "read")
	}

	if s.Write {
		scopes = append(scopes, "write")
	}

	if s.Follow {
		scopes = append(scopes, "follow")
	}

	return strings.Join(scopes, " ")
}
<|endoftext|>


<commit_before>// Package mat is an adapter for the MATLAB MAT-file API.
//
// http://www.mathworks.com/help/pdf_doc/matlab/apiext.pdf
package mat

// #cgo LDFLAGS: -lmat -lmx
// #include <string.h>
// #include <mat.h>
import "C"

import (
	"errors"
	"unsafe"
)

type File struct {
	path string
	mat *C.MATFile
}

func Open(path string, mode string) (*File, error) {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	cmode := C.CString(mode)
	defer C.free(unsafe.Pointer(cmode))

	file := &File{ path: path, mat: C.matOpen(cpath, cmode) }
	if file.mat == nil {
		goto error
	}

	return file, nil

error:
	return nil, errors.New("Cannot open the file.")
}

func (instance *File) Close() {
	C.matClose(instance.mat)
	instance.mat = nil
}

func (instance *File) PutMatrix(name string, m, n uint32,
	data []float64) error {

	var cname *C.char
	var pmatrix unsafe.Pointer

	matrix := C.mxCreateDoubleMatrix(C.size_t(m), C.size_t(n), C.mxREAL)
	if matrix == nil {
		goto error
	}

	defer C.mxDestroyArray(matrix)

	pmatrix = unsafe.Pointer(C.mxGetPr(matrix))
	if pmatrix == nil {
		goto error
	}

	C.memcpy(pmatrix, unsafe.Pointer(&data[0]), C.size_t(8 * len(data)))

	cname = C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	if C.matPutVariable(instance.mat, cname, matrix) != 0 {
		goto error
	}

	return nil

error:
	return errors.New("Cannot write into the file.")
}
<commit_msg>Stylistic changes<commit_after>// Package mat is an adapter for the MATLAB MAT-file API.
//
// http://www.mathworks.com/help/pdf_doc/matlab/apiext.pdf
package mat

// #cgo LDFLAGS: -lmat -lmx
// #include <string.h>
// #include <mat.h>
import "C"

import (
	"errors"
	"unsafe"
)

type File struct {
	path string
	mat *C.MATFile
}

func Open(path string, mode string) (*File, error) {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	cmode := C.CString(mode)
	defer C.free(unsafe.Pointer(cmode))

	file := &File{ path: path, mat: C.matOpen(cpath, cmode) }
	if file.mat == nil {
		goto error
	}

	return file, nil

error:
	return nil, errors.New("Cannot open the file.")
}

func (f *File) Close() {
	C.matClose(f.mat)
	f.mat = nil
}

func (f *File) PutMatrix(name string, rows, cols uint32,
	data []float64) error {

	var cname *C.char
	var pmatrix unsafe.Pointer

	matrix := C.mxCreateDoubleMatrix(C.size_t(rows), C.size_t(cols), C.mxREAL)
	if matrix == nil {
		goto error
	}

	defer C.mxDestroyArray(matrix)

	pmatrix = unsafe.Pointer(C.mxGetPr(matrix))
	if pmatrix == nil {
		goto error
	}

	C.memcpy(pmatrix, unsafe.Pointer(&data[0]), C.size_t(8 * len(data)))

	cname = C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	if C.matPutVariable(f.mat, cname, matrix) != 0 {
		goto error
	}

	return nil

error:
	return errors.New("Cannot write into the file.")
}
<|endoftext|>


<commit_before>/*
Package matt provides a relatively bare bones blackbox for running Matt and
easily gathering results like RMSD, core length and a p-value.

This package also provides RunAll which can be used to run many matt instances
in parallel.

Please see the 'rmsd' example in the examples directory for a full and complete
working example using three samples from the PDB.
*/
package matt
<commit_msg>Citation and web site.<commit_after>/*
Package matt provides a relatively bare bones blackbox for running Matt and
easily gathering results like RMSD, core length and a p-value.

This package also provides RunAll which can be used to run many matt instances
in parallel.

Please see the 'rmsd' example in the examples directory for a full and complete
working example using three samples from the PDB.

Matt is available on the web: http://matt.cs.tufts.edu/

M. Menke, B. Berger, L. Cowen, "Matt: Local Flexibility Aids Protein Multiple
Structure Alignment", PLOS Computational Biology, Vol. 4, No 1., 2008.
*/
package matt
<|endoftext|>


<commit_before>
<commit_msg>Initial commit<commit_after>package mcp23008

import "golang.org/x/exp/io/i2c"

const (
	MCP23008_REG_IODIR = 0x00
	MCP23008_REG_IPOL = 0x01
	MCP23008_REG_GPINTEN = 0x02
	MCP23008_REG_DEFVAL = 0x03
	MCP23008_REG_INTCON = 0x04
	MCP23008_REG_IOCON = 0x05
	MCP23008_REG_GPPU = 0x06
	MCP23008_REG_INTF = 0x07
	MCP23008_REG_INTCAP = 0x08
	MCP23008_REG_GPIO = 0x09
	MCP23008_REG_OLAT = 0x0A
)

func relayInit(d *i2c.Device) {
	// SetAllDirection
	err := d.WriteReg(MCP23008_REG_IODIR, []byte{0})
	if err != nil {
		panic(err)
	}

	// SetAllPullUp
	err = d.WriteReg(MCP23008_REG_GPPU, []byte{0})
	if err != nil {
		panic(err)
	}
}

func relaySwitchOn(d *i2c.Device, swi byte) {
	buff := []byte{0}

	stateOn := byte(swi + 1)

	d.ReadReg(MCP23008_REG_GPIO, buff)
	d.WriteReg(MCP23008_REG_GPIO,[]byte{stateOn | buff[0]})
}

func relaySwitchOff(d *i2c.Device, swi byte) {
	buff := []byte{0}

	stateOff := byte(swi + 1) ^ 0xf

	d.ReadReg(MCP23008_REG_GPIO, buff)
	d.WriteReg(MCP23008_REG_GPIO,[]byte{stateOff & buff[0]})
}

func relaySwitchRead(d *i2c.Device, swi byte) byte {
	buff := []byte{0}

	bitPos := swi + 1

	d.ReadReg(MCP23008_REG_GPIO, buff)
	state := (buff[0] & bitPos) >> swi

	return state
}

<|endoftext|>


<commit_before>package main

import (
	"github.com/huacnlee/gobackup/archive"
	"github.com/huacnlee/gobackup/compressor"
	"github.com/huacnlee/gobackup/config"
	"github.com/huacnlee/gobackup/database"
	"github.com/huacnlee/gobackup/encryptor"
	"github.com/huacnlee/gobackup/helper"
	"github.com/huacnlee/gobackup/logger"
	"github.com/huacnlee/gobackup/storage"
)

// Model class
type Model struct {
	Config config.ModelConfig
}

// Perform model
func (ctx Model) perform() {
	logger.Info("======== " + ctx.Config.Name + " ========")
	logger.Info("WorkDir:", ctx.Config.DumpPath+"\n")
	defer ctx.cleanup()

	err := database.Run(ctx.Config)
	if err != nil {
		logger.Error(err)
		return
	}

	if ctx.Config.Archive != nil {
		err = archive.Run(ctx.Config)
		if err != nil {
			logger.Error(err)
			return
		}
	}

	archivePath, err := compressor.Run(ctx.Config)
	if err != nil {
		logger.Error(err)
		return
	}

	archivePath, err = encryptor.Run(archivePath, ctx.Config)
	if err != nil {
		logger.Error(err)
		return
	}

	err = storage.Run(ctx.Config, archivePath)
	if err != nil {
		logger.Error(err)
		return
	}

}

// Cleanup model temp files
func (ctx Model) cleanup() {
	logger.Info("Cleanup temp dir...\n")
	helper.Exec("rm", "-rf", ctx.Config.DumpPath)
	logger.Info("======= End " + ctx.Config.Name + " =======\n\n")
}
<commit_msg>Stderr when cleanup failed<commit_after>package main

import (
	"github.com/huacnlee/gobackup/archive"
	"github.com/huacnlee/gobackup/compressor"
	"github.com/huacnlee/gobackup/config"
	"github.com/huacnlee/gobackup/database"
	"github.com/huacnlee/gobackup/encryptor"
	"github.com/huacnlee/gobackup/helper"
	"github.com/huacnlee/gobackup/logger"
	"github.com/huacnlee/gobackup/storage"
)

// Model class
type Model struct {
	Config config.ModelConfig
}

// Perform model
func (ctx Model) perform() {
	logger.Info("======== " + ctx.Config.Name + " ========")
	logger.Info("WorkDir:", ctx.Config.DumpPath+"\n")
	defer ctx.cleanup()

	err := database.Run(ctx.Config)
	if err != nil {
		logger.Error(err)
		return
	}

	if ctx.Config.Archive != nil {
		err = archive.Run(ctx.Config)
		if err != nil {
			logger.Error(err)
			return
		}
	}

	archivePath, err := compressor.Run(ctx.Config)
	if err != nil {
		logger.Error(err)
		return
	}

	archivePath, err = encryptor.Run(archivePath, ctx.Config)
	if err != nil {
		logger.Error(err)
		return
	}

	err = storage.Run(ctx.Config, archivePath)
	if err != nil {
		logger.Error(err)
		return
	}

}

// Cleanup model temp files
func (ctx Model) cleanup() {
	logger.Info("Cleanup temp dir...\n")
	_, err := helper.Exec("rm", "-rf", ctx.Config.DumpPath)
	if err != nil {
		logger.Error("Cleanup temp dir "+ctx.Config.DumpPath+" error:", err)
	}
	logger.Info("======= End " + ctx.Config.Name + " =======\n\n")
}
<|endoftext|>


<commit_before>// dfo: Quick script to generate symlinks to your dotfiles

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/FranGM/simplelog"
	"gopkg.in/yaml.v2"
)

type dfoConfig struct {
	RepoDir string
	HomeDir string
	WorkDir string
	Repo    string
	Noop    bool
	Verbose bool
	Yaml    dotfilesDef
}

type dotfilesDef struct {
	Files map[string]string
}

var config dfoConfig

func init() {
	config.HomeDir = os.Getenv("HOME")
	flag.StringVar(&config.WorkDir, "workdir", filepath.Join(config.HomeDir, ".dfo"), "Work directory for dfo (will be used to store backups and dotfiles git repo)")
	flag.StringVar(&config.Repo, "gitrepo", "", "Remote git repo that holds your dotfiles")
	flag.BoolVar(&config.Noop, "noop", false, "Run in noop mode (just do a dry-run)")
	flag.BoolVar(&config.Verbose, "verbose", false, "Verbose output")

	flag.Parse()
	config.RepoDir = filepath.Join(config.WorkDir, "dotfiles")

	if err := initWorkDir(); err != nil {
		log.Fatal(err)
	}

	if config.Verbose {
		simplelog.SetThreshold(simplelog.LevelDebug)
	} else {
		simplelog.SetThreshold(simplelog.LevelInfo)
	}

	configPath := filepath.Join(config.RepoDir, "dfo.yaml")
	configBytes, err := ioutil.ReadFile(configPath)
	if err != nil {
		log.Fatal(err)
	}

	err = yaml.Unmarshal(configBytes, &config.Yaml)
	if err != nil {
		log.Fatal(err)
	}
}

func initWorkDir() error {
	var perm os.FileMode = 0755
	if err := os.MkdirAll(config.WorkDir, perm); err != nil {
		return err
	}
	backupDir := filepath.Join(config.WorkDir, "backups")
	if err := os.MkdirAll(backupDir, perm); err != nil {
		return err
	}

	// We should have a clone of our git dotfiles repo here, create it if it doesn't exist
	_, err := os.Stat(config.RepoDir)

	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}

		// If no git repo has been defined and we don't have a working repo already we have a problem
		if config.Repo == "" {
			simplelog.Fatal.Printf("No git repo has been specified and no current working repo in %q, aborting", config.RepoDir)
		}

		simplelog.Info.Printf("Repo doesn't exist, cloning %q into %q...", config.Repo, config.RepoDir)
		if err := initializeGitRepo(); err != nil {
			return err
		}
		simplelog.Info.Printf("Repo cloned.")
	}

	// TODO: If a gitrepo has been specified, check that it's the same that we already have?

	simplelog.Info.Printf("Updating repo/submodules (might take a while if it's the first time)")
	if err := updateGitRepo(); err != nil {
		return err
	}
	simplelog.Info.Printf("...Done")

	return nil
}

// Clone our dotfiles git repo into our dfo working directory
func initializeGitRepo() error {
	cmd := exec.Command("git", "clone", config.Repo, config.RepoDir)

	var e bytes.Buffer
	cmd.Stderr = &e

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s: %s\n", err.Error(), e.String())
	}
	return nil
}

// Does a git pull from the remote dotfiles git repo into our working copy
func updateGitRepo() error {

	var e bytes.Buffer

	simplelog.Debug.Printf("Fetching updates from remote git repo...")
	// Do a git pull
	cmd := exec.Command("git", "pull")
	cmd.Dir = config.RepoDir
	cmd.Stderr = &e
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s: %s\n", err.Error(), e.String())
	}

	simplelog.Debug.Printf("Updating submodules...")
	// Initialize submodules
	cmd = exec.Command("git", "submodule", "update", "--init", "--recursive")
	cmd.Dir = config.RepoDir
	cmd.Stderr = &e
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s: %s\n", err.Error(), e.String())
	}

	return nil
}

// getBackupDirName generates a directory name to store backups
//   based on the current time.
func getBackupDirName() string {
	t := time.Now()
	b, _ := t.MarshalText()

	curDate := string(b)
	dirName := fmt.Sprintf("backups/dfo_backup_%v", curDate)
	return filepath.Join(config.WorkDir, dirName)
}

// createBackupDir creates a backup directory for the dotfiles.
// If directory already exists no errors will be reported
func createBackupDir(backupDir string) error {
	simplelog.Debug.Printf("Ensuring backup directory (%q) exists", backupDir)
	if config.Noop {
		return nil
	}
	err := os.Mkdir(backupDir, 0755)
	if os.IsExist(err) {
		return nil
	}
	return err
}

// fileNeedsUpdating returns true if the file should be updated. This means either:
//   - File doesn't exist
//   - File is not a symlink
//   - File is a symlink to a different file
// Returns: needsUpdate, err
func fileNeedsUpdating(path string, newSrc string) (bool, error) {
	targetPath := filepath.Join(config.HomeDir, path)
	// We don't really care if it's a symlink or not, we just want to know if it's the same symlink we're going to create
	fi, err := os.Lstat(targetPath)
	if err != nil {
		if os.IsNotExist(err) {
			return true, nil
		}
		return false, err
	}

	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
		linkTarget, err := os.Readlink(targetPath)
		if err != nil {
			return false, err
		}
		absSrc := filepath.Join(config.RepoDir, newSrc)
		// TODO: There's probably a better way of comparing them
		if absSrc == linkTarget {
			return false, nil
		}
	}
	return true, nil
}

// backupFile takes a backup of the given file and stores it in the backup directory
func backupFile(path string, backupDir string) error {
	simplelog.Info.Printf("Backing up %q", path)

	srcPath := filepath.Join(config.HomeDir, path)
	targetBackupPath := filepath.Join(backupDir, path)
	targetDir := filepath.Dir(targetBackupPath)

	// If there's no source file there's nothing to backup
	_, err := os.Stat(srcPath)
	if os.IsNotExist(err) {
		return nil
	}

	// Create backup directory if it doesn't exist already
	err = createBackupDir(backupDir)
	if err != nil {
		return err
	}

	simplelog.Debug.Printf("Ensuring %q exists before backing up file", targetDir)
	// Create any subdirectories we might need
	if !config.Noop {
		err = os.MkdirAll(targetDir, 0755)
		if err != nil {
			return err
		}
	}

	simplelog.Debug.Printf("Backing up %q into %q", srcPath, targetBackupPath)
	if config.Noop {
		return nil
	}
	err = os.Link(srcPath, targetBackupPath)
	return err
}

// replaceFile replaces a existing file with a symlink to src
// target file should have been backed up previously
func replaceFile(target string, src string, backupDir string) error {
	targetPath := filepath.Join(config.HomeDir, target)

	err := backupFile(targetPath, backupDir)
	if err != nil {
		return err
	}

	targetDir := filepath.Dir(targetPath)

	simplelog.Debug.Printf("Deleting %q", targetPath)
	if !config.Noop {
		err = os.Remove(targetPath)
		if err != nil && !os.IsNotExist(err) {
			return err
		}
	}

	simplelog.Debug.Printf("Making sure %q exists", targetDir)
	if !config.Noop {
		// Make sure that the directory holding our file exists
		err = os.MkdirAll(targetDir, 0755)
		if err != nil {
			return err
		}
	}

	// Paths in dfo.yaml can be either absolute or relative to our dotfiles repo
	absSrc := src
	if !filepath.IsAbs(src) {
		absSrc = filepath.Join(config.RepoDir, src)
	}

	simplelog.Info.Printf("%q -> %q", absSrc, targetPath)
	if config.Noop {
		return nil
	}
	err = os.Symlink(absSrc, targetPath)
	return err
}

func main() {
	backupDir := getBackupDirName()

	for target, src := range config.Yaml.Files {
		needsUpdate, err := fileNeedsUpdating(target, src)
		if err != nil {
			log.Fatal(err)
		}

		if !needsUpdate {
			simplelog.Debug.Printf("No changes needed for %v", target)
			continue
		}

		err = replaceFile(target, src, backupDir)
		if err != nil {
			simplelog.Fatal.Printf("Error replacing file: %q", err)
		}
	}
}
<commit_msg>Added backup flag. Able to backup directories now<commit_after>// dfo: Quick script to generate symlinks to your dotfiles

package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/FranGM/simplelog"
	"gopkg.in/yaml.v2"
)

type dfoConfig struct {
	RepoDir string
	HomeDir string
	WorkDir string
	Repo    string
	Noop    bool
	Verbose bool
	Backup  bool
	Yaml    dotfilesDef
}

type dotfilesDef struct {
	Files map[string]string
}

var config dfoConfig

func init() {
	config.HomeDir = os.Getenv("HOME")
	flag.StringVar(&config.WorkDir, "workdir", filepath.Join(config.HomeDir, ".dfo"), "Work directory for dfo (will be used to store backups and dotfiles git repo)")
	flag.StringVar(&config.Repo, "gitrepo", "", "Remote git repo that holds your dotfiles")
	flag.BoolVar(&config.Noop, "noop", false, "Run in noop mode (just do a dry-run)")
	flag.BoolVar(&config.Verbose, "verbose", false, "Verbose output")
	flag.BoolVar(&config.Backup, "backup", true, "Perform backups of files that are updated")

	flag.Parse()
	config.RepoDir = filepath.Join(config.WorkDir, "dotfiles")

	if err := initWorkDir(); err != nil {
		log.Fatal(err)
	}

	if config.Verbose {
		simplelog.SetThreshold(simplelog.LevelDebug)
	} else {
		simplelog.SetThreshold(simplelog.LevelInfo)
	}

	configPath := filepath.Join(config.RepoDir, "dfo.yaml")
	configBytes, err := ioutil.ReadFile(configPath)
	if err != nil {
		log.Fatal(err)
	}

	err = yaml.Unmarshal(configBytes, &config.Yaml)
	if err != nil {
		log.Fatal(err)
	}
}

func initWorkDir() error {
	var perm os.FileMode = 0755
	if err := os.MkdirAll(config.WorkDir, perm); err != nil {
		return err
	}
	backupDir := filepath.Join(config.WorkDir, "backups")
	if err := os.MkdirAll(backupDir, perm); err != nil {
		return err
	}

	// We should have a clone of our git dotfiles repo here, create it if it doesn't exist
	_, err := os.Stat(config.RepoDir)

	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}

		// If no git repo has been defined and we don't have a working repo already we have a problem
		if config.Repo == "" {
			simplelog.Fatal.Printf("No git repo has been specified and no current working repo in %q, aborting", config.RepoDir)
		}

		simplelog.Info.Printf("Repo doesn't exist, cloning %q into %q...", config.Repo, config.RepoDir)
		if err := initializeGitRepo(); err != nil {
			return err
		}
		simplelog.Info.Printf("Repo cloned.")
	}

	// TODO: If a gitrepo has been specified, check that it's the same that we already have?

	simplelog.Info.Printf("Updating repo/submodules (might take a while if it's the first time)")
	if err := updateGitRepo(); err != nil {
		return err
	}
	simplelog.Info.Printf("...Done")

	return nil
}

// Clone our dotfiles git repo into our dfo working directory
func initializeGitRepo() error {
	cmd := exec.Command("git", "clone", config.Repo, config.RepoDir)

	var e bytes.Buffer
	cmd.Stderr = &e

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s: %s\n", err.Error(), e.String())
	}
	return nil
}

// Does a git pull from the remote dotfiles git repo into our working copy
func updateGitRepo() error {

	var e bytes.Buffer

	simplelog.Debug.Printf("Fetching updates from remote git repo...")
	// Do a git pull
	cmd := exec.Command("git", "pull")
	cmd.Dir = config.RepoDir
	cmd.Stderr = &e
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s: %s\n", err.Error(), e.String())
	}

	simplelog.Debug.Printf("Updating submodules...")
	// Initialize submodules
	cmd = exec.Command("git", "submodule", "update", "--init", "--recursive")
	cmd.Dir = config.RepoDir
	cmd.Stderr = &e
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("%s: %s\n", err.Error(), e.String())
	}

	return nil
}

func copyFile(srcPath string, destPath string) (err error) {
	srcFile, err := os.Open(srcPath)
	if err != nil {
		return err
	}

	defer srcFile.Close()

	destFile, err := os.Create(destPath)
	if err != nil {
		return err
	}

	defer destFile.Close()

	_, err = io.Copy(destFile, srcFile)
	if err != nil {
		return err
	}

	fi, err := os.Stat(srcPath)
	if err != nil {
		return err
	}

	return os.Chmod(destPath, fi.Mode())
}

func copyDir(srcPath string, destPath string) (err error) {

	srcInfo, err := os.Stat(srcPath)
	if err != nil {
		return err
	}

	// create dest dir
	err = os.MkdirAll(destPath, srcInfo.Mode())
	if err != nil {
		return err
	}

	dir, _ := os.Open(srcPath)
	objects, err := dir.Readdir(-1)

	for _, obj := range objects {
		srcFile := filepath.Join(srcPath, obj.Name())
		destFile := filepath.Join(destPath, obj.Name())

		if obj.IsDir() {
			err = copyDir(srcFile, destFile)
			if err != nil {
				return err
			}
		} else {
			err = copyFile(srcFile, destFile)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// getBackupDirName generates a directory name to store backups
//   based on the current time.
func getBackupDirName() string {
	t := time.Now()
	b, _ := t.MarshalText()

	curDate := string(b)
	dirName := fmt.Sprintf("backups/dfo_backup_%v", curDate)
	return filepath.Join(config.WorkDir, dirName)
}

// createBackupDir creates a backup directory for the dotfiles.
// If directory already exists no errors will be reported
func createBackupDir(backupDir string) error {
	simplelog.Debug.Printf("Ensuring backup directory (%q) exists", backupDir)
	if config.Noop {
		return nil
	}
	err := os.Mkdir(backupDir, 0755)
	if os.IsExist(err) {
		return nil
	}
	return err
}

// fileNeedsUpdating returns true if the file should be updated. This means either:
//   - File doesn't exist
//   - File is not a symlink
//   - File is a symlink to a different file
// Returns: needsUpdate, err
func fileNeedsUpdating(path string, newSrc string) (bool, error) {
	targetPath := filepath.Join(config.HomeDir, path)
	// We don't really care if it's a symlink or not, we just want to know if it's the same symlink we're going to create
	fi, err := os.Lstat(targetPath)
	if err != nil {
		if os.IsNotExist(err) {
			return true, nil
		}
		return false, err
	}

	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
		linkTarget, err := os.Readlink(targetPath)
		if err != nil {
			return false, err
		}
		absSrc := filepath.Join(config.RepoDir, newSrc)
		// TODO: There's probably a better way of comparing them
		if absSrc == linkTarget {
			return false, nil
		}
	}
	return true, nil
}

// backupFile takes a backup of the given file and stores it in the backup directory
// path of the file to be backed up is relative to the user's home dir
func backupFile(path string, backupDir string) error {
	simplelog.Info.Printf("Backing up %q", path)

	srcPath := filepath.Join(config.HomeDir, path)
	targetBackupPath := filepath.Join(backupDir, path)
	targetDir := filepath.Dir(targetBackupPath)

	// If there's no source file there's nothing to backup
	fi, err := os.Stat(srcPath)
	if os.IsNotExist(err) {
		return nil
	}

	// Create backup directory if it doesn't exist already
	err = createBackupDir(backupDir)
	if err != nil {
		return err
	}

	simplelog.Debug.Printf("Ensuring %q exists before backing up file", targetDir)
	// Create any subdirectories we might need
	if !config.Noop {
		err = os.MkdirAll(targetDir, 0755)
		if err != nil {
			return err
		}
	}

	simplelog.Debug.Printf("Backing up %q into %q", srcPath, targetBackupPath)

	if config.Noop {
		return nil
	}

	if fi.IsDir() {
		return copyDir(srcPath, targetBackupPath)
	}

	return os.Link(srcPath, targetBackupPath)
}

// replaceFile replaces a existing file with a symlink to src
// target file should have been backed up previously
func replaceFile(target string, src string, backupDir string) error {
	targetPath := filepath.Join(config.HomeDir, target)

	if config.Backup {
		err := backupFile(target, backupDir)
		if err != nil {
			return err
		}
	}

	targetDir := filepath.Dir(targetPath)

	simplelog.Debug.Printf("Deleting %q", targetPath)
	if !config.Noop {
		err := os.RemoveAll(targetPath)
		if err != nil && !os.IsNotExist(err) {
			return err
		}
	}

	simplelog.Debug.Printf("Making sure %q exists", targetDir)
	if !config.Noop {
		// Make sure that the directory holding our file exists
		err := os.MkdirAll(targetDir, 0755)
		if err != nil {
			return err
		}
	}

	// Paths in dfo.yaml can be either absolute or relative to our dotfiles repo
	absSrc := src
	if !filepath.IsAbs(src) {
		absSrc = filepath.Join(config.RepoDir, src)
	}

	simplelog.Info.Printf("%q -> %q", absSrc, targetPath)
	if config.Noop {
		return nil
	}
	err := os.Symlink(absSrc, targetPath)
	return err
}

func main() {
	backupDir := getBackupDirName()
	simplelog.Debug.Printf("Backups will be stored in %q", backupDir)

	for target, src := range config.Yaml.Files {
		needsUpdate, err := fileNeedsUpdating(target, src)
		if err != nil {
			log.Fatal(err)
		}

		if !needsUpdate {
			simplelog.Debug.Printf("No changes needed for %v", target)
			continue
		}

		err = replaceFile(target, src, backupDir)
		if err != nil {
			simplelog.Fatal.Println(err)
		}
	}
}
<|endoftext|>


<commit_before>package memalpha

import (
	"bytes"
	"fmt"
	"net"
	"os/exec"
	"reflect"
	"strconv"
	"testing"
	"time"
)

func freePort() (int, error) {
	l, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return 0, err
	}
	defer l.Close()

	return l.Addr().(*net.TCPAddr).Port, nil
}

type server struct {
	cmd    *exec.Cmd
	client *Client
}

func newServer() *server {
	return &server{}
}

func (s *server) Start() error {
	port, err := freePort()
	if err != nil {
		return err
	}

	s.cmd = exec.Command("memcached", "-p", strconv.Itoa(port))
	if err = s.cmd.Start(); err != nil {
		return err
	}

	s.client = NewClient(fmt.Sprintf("localhost:%d", port))

	// Wait a bit for the socket to appear.
	for i := 0; i < 10; i++ {
		err = s.client.ensureConnected()
		if err == nil {
			return nil
		}
		time.Sleep(time.Duration(25*i) * time.Millisecond)
	}

	return err
}

func (s *server) Shutdown() error {
	_ = s.cmd.Process.Kill()
	return s.cmd.Wait()
}

func TestLocalhost(t *testing.T) {
	memd := newServer()
	err := memd.Start()
	if err != nil {
		t.Skipf("skipping test; couldn't start memcached: %s", err)
	}
	defer memd.Shutdown()

	c := memd.client

	// Set
	err = c.Set("foo", []byte("fooval"))
	if err != nil {
		t.Fatalf("first set(foo): %v", err)
	}
	err = c.Set("foo", []byte("fooval"))
	if err != nil {
		t.Fatalf("second set(foo): %v", err)
	}

	// Get
	value, _, err := c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("fooval")) {
		t.Fatalf("get(foo) Value = %q, want fooval", value)
	}

	// Set large item
	largeKey := string(bytes.Repeat([]byte("A"), 250))
	largeValue := bytes.Repeat([]byte("A"), 1023*1024)
	err = c.Set(largeKey, largeValue)
	if err != nil {
		t.Fatalf("set(largeKey): %v", err)
	}

	// Get large item
	value, _, err = c.Get(largeKey)
	if err != nil {
		t.Fatalf("get(largeKey): %v", err)
	}
	if !bytes.Equal(value, largeValue) {
		peekLen := len(value)
		if peekLen > 10 {
			peekLen = 10
		}
		t.Fatalf("get(largeKey) Value = %q, want fooval", value[:peekLen])
	}

	// Gets
	err = c.Set("bar", []byte("barval"))
	if err != nil {
		t.Fatalf("set(bar): %v", err)
	}
	m, err := c.Gets([]string{"foo", "bar"})
	if err != nil {
		t.Fatalf("gets(foo, bar): %v", err)
	}
	keyToValue := make(map[string]string)
	for key, response := range m {
		keyToValue[key] = string(response.Value)
	}
	expected := map[string]string{"foo": "fooval", "bar": "barval"}
	if !reflect.DeepEqual(keyToValue, expected) {
		t.Fatalf("gets(foo, bar) Value = %+v, want %+v", m, expected)
	}

	// Add
	err = c.Add("baz", []byte("baz1"))
	if err != nil {
		t.Fatalf("first add(baz): %v", err)
	}
	err = c.Add("baz", []byte("baz2"))
	if err != ErrNotStored {
		t.Fatalf("second add(baz) Error = ErrNotStored, want %+v", err)
	}

	// Replace
	err = c.Set("foo", []byte("fooval"))
	if err != nil {
		t.Fatalf("set(foo): %v", err)
	}
	err = c.Replace("foo", []byte("fooval2"))
	if err != nil {
		t.Fatalf("replace(foo): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("fooval2")) {
		t.Fatalf("replace(foo, fooval2) then, get(foo) Value = %q, want fooval2", value)
	}

	// Append
	err = c.Append("foo", []byte("suffix"))
	if err != nil {
		t.Fatalf("append(foo, suffix): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("fooval2suffix")) {
		t.Fatalf("append(foo, suffix) then, get(foo) Value = %q, want fooval2suffix", value)
	}

	// Prepend
	err = c.Prepend("foo", []byte("prefix"))
	if err != nil {
		t.Fatalf("prepend(foo, prefix): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("prefixfooval2suffix")) {
		t.Fatalf("prepend(foo, prefix) then, get(foo) Value = %q, want prefixfooval2suffix", value)
	}

	// CompareAndSwap
	m, err = c.Gets([]string{"foo"})
	if err != nil {
		t.Fatalf("gets(foo): %v", err)
	}
	err = c.CompareAndSwap("foo", []byte("swapped"), m["foo"].CasID)
	if err != nil {
		t.Fatalf("cas(foo, swapped, casid): %v", err)
	}
	err = c.CompareAndSwap("foo", []byte("swapped"), m["foo"].CasID)
	if err != ErrCasConflict {
		t.Fatalf("cas(foo, swapped, casid) Error = %v, want %v", err, ErrCasConflict)
	}

	// Delete
	err = c.Delete("foo", false)
	if err != nil {
		t.Fatalf("delete(foo): %v", err)
	}
	_, _, err = c.Get("foo")
	if err != ErrCacheMiss {
		t.Fatalf("get(foo) Error = %q, want ErrCacheMiss", err)
	}

	// Delete raises ErrNotFound
	err = c.Delete("not_exists", false)
	if err != ErrNotFound {
		t.Fatalf("delete(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Increment
	err = c.Set("foo", []byte("35"))
	if err != nil {
		t.Fatalf("set(foo): %v", err)
	}
	num, err := c.Increment("foo", 7, false)
	if err != nil {
		t.Fatalf("incr(foo, 7): %v", err)
	}
	if num != 42 {
		t.Fatalf("incr(foo, 7) Value = %q, want 42", num)
	}

	// Increment raises ErrNotFound
	_, err = c.Increment("not_exists", 10, false)
	if err != ErrNotFound {
		t.Fatalf("incr(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Decrement
	num, err = c.Decrement("foo", 10, false)
	if err != nil {
		t.Fatalf("decr(foo, 10): %v", err)
	}
	if num != 32 {
		t.Fatalf("decr(foo, 10) Value = %q, want 32", num)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("32")) {
		t.Fatalf("get(foo) Value = %q, want 32", value)
	}

	// Touch
	err = c.Touch("foo", 2, false)
	if err != nil {
		t.Fatalf("touch(foo, 2): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("32")) {
		t.Fatalf("get(foo) Value = %q, want 32", value)
	}
	time.Sleep(2 * time.Second)
	_, _, err = c.Get("foo")
	if err != ErrCacheMiss {
		t.Fatalf("get(foo) Error = %q, want ErrCacheMiss", err)
	}

	// Touch raises ErrNotFound
	err = c.Touch("not_exists", 10, false)
	if err != ErrNotFound {
		t.Fatalf("touch(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Stats
	stats, err := c.Stats()
	if err != nil {
		t.Fatalf("stats(): %v", err)
	}
	if len(stats) < 2 {
		t.Fatalf("stats(): len(Value) = %q, want len(value) > 2", stats)
	}

	// StatsArg
	stats, err = c.StatsArg("slabs")
	if err != nil {
		t.Fatalf("stats(): %v", err)
	}
	if len(stats) < 2 {
		t.Fatalf("stats(): len(Value) = %q, want len(value) > 2", stats)
	}

	// FlushAll
	err = c.Set("foo", []byte("bar"))
	if err != nil {
		t.Fatalf("set(foo): %v", err)
	}
	err = c.FlushAll(0, false)
	if err != nil {
		t.Fatalf("version(): %v", err)
	}
	_, _, err = c.Get("foo")
	if err != ErrCacheMiss {
		t.Fatalf("get(foo) Error = %q, want ErrCacheMiss", err)
	}

	// Version
	ver, err := c.Version()
	if err != nil {
		t.Fatalf("version(): %v", err)
	}
	if len(ver) == 0 {
		t.Fatalf("version() Value = %q, want len(value) > 0", ver)
	}

	// Quit
	err = c.Quit()
	if err != nil {
		t.Fatalf("quit(): %v", err)
	}
	if c.conn == nil {
		t.Fatalf("net.Conn = %q, want nil", c.conn)
	}
}
<commit_msg>Test ErrNotFound of CompareAndSwap<commit_after>package memalpha

import (
	"bytes"
	"fmt"
	"net"
	"os/exec"
	"reflect"
	"strconv"
	"testing"
	"time"
)

func freePort() (int, error) {
	l, err := net.Listen("tcp", "localhost:0")
	if err != nil {
		return 0, err
	}
	defer l.Close()

	return l.Addr().(*net.TCPAddr).Port, nil
}

type server struct {
	cmd    *exec.Cmd
	client *Client
}

func newServer() *server {
	return &server{}
}

func (s *server) Start() error {
	port, err := freePort()
	if err != nil {
		return err
	}

	s.cmd = exec.Command("memcached", "-p", strconv.Itoa(port))
	if err = s.cmd.Start(); err != nil {
		return err
	}

	s.client = NewClient(fmt.Sprintf("localhost:%d", port))

	// Wait a bit for the socket to appear.
	for i := 0; i < 10; i++ {
		err = s.client.ensureConnected()
		if err == nil {
			return nil
		}
		time.Sleep(time.Duration(25*i) * time.Millisecond)
	}

	return err
}

func (s *server) Shutdown() error {
	_ = s.cmd.Process.Kill()
	return s.cmd.Wait()
}

func TestLocalhost(t *testing.T) {
	memd := newServer()
	err := memd.Start()
	if err != nil {
		t.Skipf("skipping test; couldn't start memcached: %s", err)
	}
	defer memd.Shutdown()

	c := memd.client

	// Set
	err = c.Set("foo", []byte("fooval"))
	if err != nil {
		t.Fatalf("first set(foo): %v", err)
	}
	err = c.Set("foo", []byte("fooval"))
	if err != nil {
		t.Fatalf("second set(foo): %v", err)
	}

	// Get
	value, _, err := c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("fooval")) {
		t.Fatalf("get(foo) Value = %q, want fooval", value)
	}

	// Set large item
	largeKey := string(bytes.Repeat([]byte("A"), 250))
	largeValue := bytes.Repeat([]byte("A"), 1023*1024)
	err = c.Set(largeKey, largeValue)
	if err != nil {
		t.Fatalf("set(largeKey): %v", err)
	}

	// Get large item
	value, _, err = c.Get(largeKey)
	if err != nil {
		t.Fatalf("get(largeKey): %v", err)
	}
	if !bytes.Equal(value, largeValue) {
		peekLen := len(value)
		if peekLen > 10 {
			peekLen = 10
		}
		t.Fatalf("get(largeKey) Value = %q, want fooval", value[:peekLen])
	}

	// Gets
	err = c.Set("bar", []byte("barval"))
	if err != nil {
		t.Fatalf("set(bar): %v", err)
	}
	m, err := c.Gets([]string{"foo", "bar"})
	if err != nil {
		t.Fatalf("gets(foo, bar): %v", err)
	}
	keyToValue := make(map[string]string)
	for key, response := range m {
		keyToValue[key] = string(response.Value)
	}
	expected := map[string]string{"foo": "fooval", "bar": "barval"}
	if !reflect.DeepEqual(keyToValue, expected) {
		t.Fatalf("gets(foo, bar) Value = %+v, want %+v", m, expected)
	}

	// Add
	err = c.Add("baz", []byte("baz1"))
	if err != nil {
		t.Fatalf("first add(baz): %v", err)
	}
	err = c.Add("baz", []byte("baz2"))
	if err != ErrNotStored {
		t.Fatalf("second add(baz) Error = ErrNotStored, want %+v", err)
	}

	// Replace
	err = c.Set("foo", []byte("fooval"))
	if err != nil {
		t.Fatalf("set(foo): %v", err)
	}
	err = c.Replace("foo", []byte("fooval2"))
	if err != nil {
		t.Fatalf("replace(foo): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("fooval2")) {
		t.Fatalf("replace(foo, fooval2) then, get(foo) Value = %q, want fooval2", value)
	}

	// Append
	err = c.Append("foo", []byte("suffix"))
	if err != nil {
		t.Fatalf("append(foo, suffix): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("fooval2suffix")) {
		t.Fatalf("append(foo, suffix) then, get(foo) Value = %q, want fooval2suffix", value)
	}

	// Prepend
	err = c.Prepend("foo", []byte("prefix"))
	if err != nil {
		t.Fatalf("prepend(foo, prefix): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("prefixfooval2suffix")) {
		t.Fatalf("prepend(foo, prefix) then, get(foo) Value = %q, want prefixfooval2suffix", value)
	}

	// CompareAndSwap
	m, err = c.Gets([]string{"foo"})
	if err != nil {
		t.Fatalf("gets(foo): %v", err)
	}
	err = c.CompareAndSwap("foo", []byte("swapped"), m["foo"].CasID)
	if err != nil {
		t.Fatalf("cas(foo, swapped, casid): %v", err)
	}
	err = c.CompareAndSwap("foo", []byte("swapped"), m["foo"].CasID)
	if err != ErrCasConflict {
		t.Fatalf("cas(foo, swapped, casid) Error = %v, want %v", err, ErrCasConflict)
	}

	// CompareAndSwap raises ErrNotFound
	err = c.CompareAndSwap("not_exists", []byte("ignored"), 42)
	if err != ErrNotFound {
		t.Fatalf("cas(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Delete
	err = c.Delete("foo", false)
	if err != nil {
		t.Fatalf("delete(foo): %v", err)
	}
	_, _, err = c.Get("foo")
	if err != ErrCacheMiss {
		t.Fatalf("get(foo) Error = %q, want ErrCacheMiss", err)
	}

	// Delete raises ErrNotFound
	err = c.Delete("not_exists", false)
	if err != ErrNotFound {
		t.Fatalf("delete(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Increment
	err = c.Set("foo", []byte("35"))
	if err != nil {
		t.Fatalf("set(foo): %v", err)
	}
	num, err := c.Increment("foo", 7, false)
	if err != nil {
		t.Fatalf("incr(foo, 7): %v", err)
	}
	if num != 42 {
		t.Fatalf("incr(foo, 7) Value = %q, want 42", num)
	}

	// Increment raises ErrNotFound
	_, err = c.Increment("not_exists", 10, false)
	if err != ErrNotFound {
		t.Fatalf("incr(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Decrement
	num, err = c.Decrement("foo", 10, false)
	if err != nil {
		t.Fatalf("decr(foo, 10): %v", err)
	}
	if num != 32 {
		t.Fatalf("decr(foo, 10) Value = %q, want 32", num)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("32")) {
		t.Fatalf("get(foo) Value = %q, want 32", value)
	}

	// Touch
	err = c.Touch("foo", 2, false)
	if err != nil {
		t.Fatalf("touch(foo, 2): %v", err)
	}
	value, _, err = c.Get("foo")
	if err != nil {
		t.Fatalf("get(foo): %v", err)
	}
	if !bytes.Equal(value, []byte("32")) {
		t.Fatalf("get(foo) Value = %q, want 32", value)
	}
	time.Sleep(2 * time.Second)
	_, _, err = c.Get("foo")
	if err != ErrCacheMiss {
		t.Fatalf("get(foo) Error = %q, want ErrCacheMiss", err)
	}

	// Touch raises ErrNotFound
	err = c.Touch("not_exists", 10, false)
	if err != ErrNotFound {
		t.Fatalf("touch(not_exists) Error = %q, want ErrNotFound", err)
	}

	// Stats
	stats, err := c.Stats()
	if err != nil {
		t.Fatalf("stats(): %v", err)
	}
	if len(stats) < 2 {
		t.Fatalf("stats(): len(Value) = %q, want len(value) > 2", stats)
	}

	// StatsArg
	stats, err = c.StatsArg("slabs")
	if err != nil {
		t.Fatalf("stats(): %v", err)
	}
	if len(stats) < 2 {
		t.Fatalf("stats(): len(Value) = %q, want len(value) > 2", stats)
	}

	// FlushAll
	err = c.Set("foo", []byte("bar"))
	if err != nil {
		t.Fatalf("set(foo): %v", err)
	}
	err = c.FlushAll(0, false)
	if err != nil {
		t.Fatalf("version(): %v", err)
	}
	_, _, err = c.Get("foo")
	if err != ErrCacheMiss {
		t.Fatalf("get(foo) Error = %q, want ErrCacheMiss", err)
	}

	// Version
	ver, err := c.Version()
	if err != nil {
		t.Fatalf("version(): %v", err)
	}
	if len(ver) == 0 {
		t.Fatalf("version() Value = %q, want len(value) > 0", ver)
	}

	// Quit
	err = c.Quit()
	if err != nil {
		t.Fatalf("quit(): %v", err)
	}
	if c.conn == nil {
		t.Fatalf("net.Conn = %q, want nil", c.conn)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"strconv"

	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/kyokomi/emoji"
)

func postPoll(bot *tgbotapi.BotAPI, p *poll, chatid int64) (tgbotapi.Message, error) {
	share := tgbotapi.InlineKeyboardButton{
		Text:              locSharePoll,
		SwitchInlineQuery: &p.Question,
	}
	new := tgbotapi.NewInlineKeyboardButtonData(locCreateNewPoll, createPollQuery)

	buttons := tgbotapi.NewInlineKeyboardRow(share, new)
	markup := tgbotapi.NewInlineKeyboardMarkup(buttons)
	messageTxt := locFinishedCreatingPoll
	messageTxt += p.Question + "\n\n"

	for i, o := range p.Options {
		messageTxt += strconv.Itoa(i+1) + ") " + o.Text + "\n"
	}
	msg := tgbotapi.NewMessage(chatid, messageTxt)
	msg.ReplyMarkup = markup

	return bot.Send(msg)
}

func sendMainMenuMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update) (tgbotapi.Message, error) {
	buttons := make([]tgbotapi.InlineKeyboardButton, 0)
	buttons = append(buttons, tgbotapi.NewInlineKeyboardButtonData("create poll", createPollQuery))
	markup := tgbotapi.NewInlineKeyboardMarkup(buttons)
	messageTxt := locMainMenu
	msg := tgbotapi.NewMessage(int64(update.Message.From.ID), messageTxt)
	msg.ReplyMarkup = markup

	return bot.Send(msg)
}

func sendInterMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update, p *poll) (tgbotapi.Message, error) {
	//shareButton := tgbotapi.InlineKeyboardButton{
	//Text:              locSharePoll,
	//SwitchInlineQuery: &p.Question,
	//}
	pollDoneButton := tgbotapi.NewInlineKeyboardButtonData(
		locPollDoneButton, fmt.Sprintf("%s:%d", pollDoneQuery, p.ID))

	buttons := make([]tgbotapi.InlineKeyboardButton, 0)
	buttons = append(buttons, pollDoneButton)
	//buttons = append(buttons, shareButton)

	markup := tgbotapi.NewInlineKeyboardMarkup(buttons)
	messageTxt := locAddedOption
	messageTxt += p.Question + "\n\n"

	for i, o := range p.Options {
		messageTxt += strconv.Itoa(i+1) + ") " + o.Text + "\n"
	}
	msg := tgbotapi.NewMessage(int64(update.Message.From.ID), messageTxt)
	msg.ReplyMarkup = markup

	return bot.Send(msg)
}

func sendNewQuestionMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	msg := tgbotapi.NewMessage(int64(update.CallbackQuery.From.ID), locNewQuestion)
	_, err := bot.Send(&msg)
	if err != nil {
		return fmt.Errorf("could not send message: %v", err)
	}

	err = st.SaveState(update.CallbackQuery.From.ID, -1, waitingForQuestion)
	if err != nil {
		return fmt.Errorf("could not change state to waiting for questions: %v", err)
	}
	return nil
}

func sendEditMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update, p *poll) (tgbotapi.Message, error) {
	body := "This is the poll currently selected:\n```\n"
	body += p.Question + "\n"
	for i, o := range p.Options {
		body += fmt.Sprintf("%d. %s", i+1, o.Text) + "\n"
	}
	body += "```\n\n"
	msg := tgbotapi.NewMessage(int64(update.Message.From.ID), body)
	msg.ParseMode = tgbotapi.ModeHTML

	msg.ReplyMarkup = buildEditMarkup(p, false, false)

	return bot.Send(&msg)
}

func buildPollMarkup(p *poll) *tgbotapi.InlineKeyboardMarkup {
	buttonrows := make([][]tgbotapi.InlineKeyboardButton, 0) //len(p.Options), len(p.Options))
	row := -1

	votesForOption := make(map[int]int)
	for _, o := range p.Options {
		for _, a := range p.Answers {
			if a.OptionID == o.ID {
				votesForOption[o.ID]++
			}
		}
	}

	for _, o := range p.Options {
		textWidth := 0
		if row != -1 {
			for _, b := range buttonrows[row] {
				textWidth += len(b.Text)
			}
		}
		textWidth += len(o.Text)
		if row == -1 || textWidth > 30 {
			row++
			buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))
		}
		label := fmt.Sprintf("%s (%d)", o.Text, votesForOption[o.ID])
		callback := fmt.Sprintf("%d:%d", p.ID, o.ID)
		button := tgbotapi.NewInlineKeyboardButtonData(label, callback)
		buttonrows[row] = append(buttonrows[row], button)
	}
	markup := tgbotapi.NewInlineKeyboardMarkup(buttonrows...)

	return &markup
}

func buildPollListing(p *poll, st Store) (listing string) {
	polledUsers := make(map[int]struct{})
	listOfUsers := make([][]*tgbotapi.User, len(p.Options))
	votesForOption := make(map[int]int)
	for i, o := range p.Options {
		for _, a := range p.Answers {
			if a.OptionID == o.ID {
				votesForOption[o.ID]++
				u, err := st.GetUser(a.UserID)
				if err != nil {
					log.Printf("could not get user: %v", err)
					listOfUsers[i] = append(listOfUsers[i], &tgbotapi.User{ID: a.UserID})
					continue
				}
				polledUsers[u.ID] = struct{}{}
				listOfUsers[i] = append(listOfUsers[i], u)
			}
		}
	}

	listing += emoji.Sprintf(":bar_chart:%s\n", p.Question)
	//log.Printf("Create listing for question: %s\n", p.Question)

	for i, o := range p.Options {
		var part string
		if len(p.Answers) > 0 {
			part = fmt.Sprintf(" (%.0f%%)", 100.*float64(votesForOption[o.ID])/float64(len(polledUsers)))
			if votesForOption[o.ID] != o.Ctr {
				log.Printf("Counter for option #%d is off: %d stored vs. %d counted", o.ID, o.Ctr, votesForOption[o.ID])
			}
		}
		listing += fmt.Sprintf("\n%s%s", o.Text, part)

		usersOnAnswer := len(listOfUsers[i])
		if len(p.Answers) < maxNumberOfUsersListed && usersOnAnswer > 0 {
			for j := 0; j+1 < usersOnAnswer; j++ {
				listing += "\n\u251C " + getDisplayUserName(listOfUsers[i][j])
			}
			listing += "\n\u2514 " + getDisplayUserName(listOfUsers[i][usersOnAnswer-1])
		}
		listing += "\n"
	}
	listing += emoji.Sprint(fmt.Sprintf("\n%d :busts_in_silhouette:\n", len(polledUsers)))
	return listing
}

func buildEditMarkup(p *poll, noOlder, noNewer bool) *tgbotapi.InlineKeyboardMarkup {
	query := fmt.Sprintf("e:%d", p.ID)

	buttonrows := make([][]tgbotapi.InlineKeyboardButton, 0)
	buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))
	buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))
	buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))

	buttonLast := tgbotapi.NewInlineKeyboardButtonData("\u2B05", query+":-")
	buttonNext := tgbotapi.NewInlineKeyboardButtonData("\u27A1", query+":+")
	if noOlder {
		buttonLast = tgbotapi.NewInlineKeyboardButtonData("\u2B05", "dummy")
	}
	if noNewer {
		buttonNext = tgbotapi.NewInlineKeyboardButtonData("\u27A1", "dummy")
	}
	buttonrows[0] = append(buttonrows[0], buttonLast, buttonNext)
	buttonInactive := tgbotapi.NewInlineKeyboardButtonData(locToggleOpen, query+":c")
	if isInactive(p) {
		buttonInactive = tgbotapi.NewInlineKeyboardButtonData(locToggleInactive, query+":c")
	}
	buttonMultipleChoice := tgbotapi.NewInlineKeyboardButtonData(locToggleSingleChoice, query+":m")
	// if isMultipleChoice(p) {
	// 	buttonMultipleChoice = tgbotapi.NewInlineKeyboardButtonData(locToggleMultipleChoice, query+":m")
	// }
	buttonrows[1] = append(buttonrows[1], buttonInactive)
	if !isMultipleChoice(p) {
		buttonrows[1] = append(buttonrows[1], buttonMultipleChoice)
	}
	buttonEditQuestion := tgbotapi.NewInlineKeyboardButtonData(locEditQuestionButton, query+":q")
	buttonAddOptions := tgbotapi.NewInlineKeyboardButtonData(locAddOptionButton, query+":o")

	buttonrows[2] = append(buttonrows[2], buttonEditQuestion, buttonAddOptions)
	markup := tgbotapi.NewInlineKeyboardMarkup(buttonrows...)

	return &markup
}

func getDisplayUserName(u *tgbotapi.User) string {
	if u.FirstName == "" && u.LastName == "" {
		return strconv.Itoa(u.ID)
	} else if u.FirstName != "" {
		name := u.FirstName
		if u.LastName != "" {
			name += " " + u.LastName
		}
		return name
	}
	return u.LastName
}
<commit_msg>escape HTML in polls<commit_after>package main

import (
	"fmt"
	"html"
	"log"
	"strconv"

	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/kyokomi/emoji"
)

func postPoll(bot *tgbotapi.BotAPI, p *poll, chatid int64) (tgbotapi.Message, error) {
	share := tgbotapi.InlineKeyboardButton{
		Text:              locSharePoll,
		SwitchInlineQuery: &p.Question,
	}
	new := tgbotapi.NewInlineKeyboardButtonData(locCreateNewPoll, createPollQuery)

	buttons := tgbotapi.NewInlineKeyboardRow(share, new)
	markup := tgbotapi.NewInlineKeyboardMarkup(buttons)
	messageTxt := locFinishedCreatingPoll
	messageTxt += p.Question + "\n\n"

	for i, o := range p.Options {
		messageTxt += strconv.Itoa(i+1) + ") " + o.Text + "\n"
	}
	msg := tgbotapi.NewMessage(chatid, messageTxt)
	msg.ReplyMarkup = markup

	return bot.Send(msg)
}

func sendMainMenuMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update) (tgbotapi.Message, error) {
	buttons := make([]tgbotapi.InlineKeyboardButton, 0)
	buttons = append(buttons, tgbotapi.NewInlineKeyboardButtonData("create poll", createPollQuery))
	markup := tgbotapi.NewInlineKeyboardMarkup(buttons)
	messageTxt := locMainMenu
	msg := tgbotapi.NewMessage(int64(update.Message.From.ID), messageTxt)
	msg.ReplyMarkup = markup

	return bot.Send(msg)
}

func sendInterMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update, p *poll) (tgbotapi.Message, error) {
	//shareButton := tgbotapi.InlineKeyboardButton{
	//Text:              locSharePoll,
	//SwitchInlineQuery: &p.Question,
	//}
	pollDoneButton := tgbotapi.NewInlineKeyboardButtonData(
		locPollDoneButton, fmt.Sprintf("%s:%d", pollDoneQuery, p.ID))

	buttons := make([]tgbotapi.InlineKeyboardButton, 0)
	buttons = append(buttons, pollDoneButton)
	//buttons = append(buttons, shareButton)

	markup := tgbotapi.NewInlineKeyboardMarkup(buttons)
	messageTxt := locAddedOption
	messageTxt += p.Question + "\n\n"

	for i, o := range p.Options {
		messageTxt += strconv.Itoa(i+1) + ") " + o.Text + "\n"
	}
	msg := tgbotapi.NewMessage(int64(update.Message.From.ID), messageTxt)
	msg.ReplyMarkup = markup

	return bot.Send(msg)
}

func sendNewQuestionMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update, st Store) error {
	msg := tgbotapi.NewMessage(int64(update.CallbackQuery.From.ID), locNewQuestion)
	_, err := bot.Send(&msg)
	if err != nil {
		return fmt.Errorf("could not send message: %v", err)
	}

	err = st.SaveState(update.CallbackQuery.From.ID, -1, waitingForQuestion)
	if err != nil {
		return fmt.Errorf("could not change state to waiting for questions: %v", err)
	}
	return nil
}

func sendEditMessage(bot *tgbotapi.BotAPI, update tgbotapi.Update, p *poll) (tgbotapi.Message, error) {
	body := "This is the poll currently selected:\n```\n"
	body += p.Question + "\n"
	for i, o := range p.Options {
		body += fmt.Sprintf("%d. %s", i+1, o.Text) + "\n"
	}
	body += "```\n\n"
	msg := tgbotapi.NewMessage(int64(update.Message.From.ID), body)
	msg.ParseMode = tgbotapi.ModeHTML

	msg.ReplyMarkup = buildEditMarkup(p, false, false)

	return bot.Send(&msg)
}

func buildPollMarkup(p *poll) *tgbotapi.InlineKeyboardMarkup {
	buttonrows := make([][]tgbotapi.InlineKeyboardButton, 0) //len(p.Options), len(p.Options))
	row := -1

	votesForOption := make(map[int]int)
	for _, o := range p.Options {
		for _, a := range p.Answers {
			if a.OptionID == o.ID {
				votesForOption[o.ID]++
			}
		}
	}

	for _, o := range p.Options {
		textWidth := 0
		if row != -1 {
			for _, b := range buttonrows[row] {
				textWidth += len(b.Text)
			}
		}
		textWidth += len(o.Text)
		if row == -1 || textWidth > 30 {
			row++
			buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))
		}
		label := fmt.Sprintf("%s (%d)", o.Text, votesForOption[o.ID])
		callback := fmt.Sprintf("%d:%d", p.ID, o.ID)
		button := tgbotapi.NewInlineKeyboardButtonData(label, callback)
		buttonrows[row] = append(buttonrows[row], button)
	}
	markup := tgbotapi.NewInlineKeyboardMarkup(buttonrows...)

	return &markup
}

func buildPollListing(p *poll, st Store) (listing string) {
	polledUsers := make(map[int]struct{})
	listOfUsers := make([][]*tgbotapi.User, len(p.Options))
	votesForOption := make(map[int]int)
	for i, o := range p.Options {
		for _, a := range p.Answers {
			if a.OptionID == o.ID {
				votesForOption[o.ID]++
				u, err := st.GetUser(a.UserID)
				if err != nil {
					log.Printf("could not get user: %v", err)
					listOfUsers[i] = append(listOfUsers[i], &tgbotapi.User{ID: a.UserID})
					continue
				}
				polledUsers[u.ID] = struct{}{}
				listOfUsers[i] = append(listOfUsers[i], u)
			}
		}
	}

	listing += emoji.Sprintf(":bar_chart:%s\n", html.EscapeString(p.Question))
	//log.Printf("Create listing for question: %s\n", p.Question)

	for i, o := range p.Options {
		var part string
		if len(p.Answers) > 0 {
			part = fmt.Sprintf(" (%.0f%%)", 100.*float64(votesForOption[o.ID])/float64(len(polledUsers)))
			if votesForOption[o.ID] != o.Ctr {
				log.Printf("Counter for option #%d is off: %d stored vs. %d counted", o.ID, o.Ctr, votesForOption[o.ID])
			}
		}
		listing += fmt.Sprintf("\n%s%s", html.EscapeString(o.Text), part)

		usersOnAnswer := len(listOfUsers[i])
		if len(p.Answers) < maxNumberOfUsersListed && usersOnAnswer > 0 {
			for j := 0; j+1 < usersOnAnswer; j++ {
				listing += "\n\u251C " + html.EscapeString(getDisplayUserName(listOfUsers[i][j]))
			}
			listing += "\n\u2514 " + html.EscapeString(getDisplayUserName(listOfUsers[i][usersOnAnswer-1]))
		}
		listing += "\n"
	}
	listing += emoji.Sprint(fmt.Sprintf("\n%d :busts_in_silhouette:\n", len(polledUsers)))
	return listing
}

func buildEditMarkup(p *poll, noOlder, noNewer bool) *tgbotapi.InlineKeyboardMarkup {
	query := fmt.Sprintf("e:%d", p.ID)

	buttonrows := make([][]tgbotapi.InlineKeyboardButton, 0)
	buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))
	buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))
	buttonrows = append(buttonrows, make([]tgbotapi.InlineKeyboardButton, 0))

	buttonLast := tgbotapi.NewInlineKeyboardButtonData("\u2B05", query+":-")
	buttonNext := tgbotapi.NewInlineKeyboardButtonData("\u27A1", query+":+")
	if noOlder {
		buttonLast = tgbotapi.NewInlineKeyboardButtonData("\u2B05", "dummy")
	}
	if noNewer {
		buttonNext = tgbotapi.NewInlineKeyboardButtonData("\u27A1", "dummy")
	}
	buttonrows[0] = append(buttonrows[0], buttonLast, buttonNext)
	buttonInactive := tgbotapi.NewInlineKeyboardButtonData(locToggleOpen, query+":c")
	if isInactive(p) {
		buttonInactive = tgbotapi.NewInlineKeyboardButtonData(locToggleInactive, query+":c")
	}
	buttonMultipleChoice := tgbotapi.NewInlineKeyboardButtonData(locToggleSingleChoice, query+":m")
	// if isMultipleChoice(p) {
	// 	buttonMultipleChoice = tgbotapi.NewInlineKeyboardButtonData(locToggleMultipleChoice, query+":m")
	// }
	buttonrows[1] = append(buttonrows[1], buttonInactive)
	if !isMultipleChoice(p) {
		buttonrows[1] = append(buttonrows[1], buttonMultipleChoice)
	}
	buttonEditQuestion := tgbotapi.NewInlineKeyboardButtonData(locEditQuestionButton, query+":q")
	buttonAddOptions := tgbotapi.NewInlineKeyboardButtonData(locAddOptionButton, query+":o")

	buttonrows[2] = append(buttonrows[2], buttonEditQuestion, buttonAddOptions)
	markup := tgbotapi.NewInlineKeyboardMarkup(buttonrows...)

	return &markup
}

func getDisplayUserName(u *tgbotapi.User) string {
	if u.FirstName == "" && u.LastName == "" {
		return strconv.Itoa(u.ID)
	} else if u.FirstName != "" {
		name := u.FirstName
		if u.LastName != "" {
			name += " " + u.LastName
		}
		return name
	}
	return u.LastName
}
<|endoftext|>