<commit_before>package vcf

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"
)

// Variant is a struct representing the fields specified in the VCF 4.2 spec. It does not support structural variants. When the variant is generated through the API of the vcf package, the required fields are guaranteed to be valid, otherwise the parsing for the variant fails and is reported.
// Multiple alternatives are parsed as separated instances of the type Variant
// All other fields are optional and will not cause parsing fails if missing or non-conformant.
type Variant struct {
	// Required fields
	Chrom string
	Pos   int
	Ref   string
	Alt   string

	ID string
	// Qual is a pointer so that it can be set to nil when it is a dot '.'
	Qual   *float64
	Filter string
	// Info is a map containing all the keys present in the INFO field, with their corresponding value. For keys without corresponding values, the value is a `true` bool.
	// No attempt at parsing is made on this field, data is raw. The only exception is for multiple alternatives data. These are reported separately for each variant
	Info map[string]interface{}

	// Genotype fields for each sample
	Samples []map[string]string

	// Optional info fields. These are the reserved fields listed on the VCF 4.2 spec, session 1.4.1, number 8. The parsing is lenient, if the fields do not conform to the expected type listed here, they will be set to nil
	// The fields are meant as helpers for common scenarios, since the generic usage is covered by the Info map
	// Definitions used in the metadata section of the header are not used
	AncestralAllele *string
	Depth           *int
	AlleleFrequency *float64
	AlleleCount     *int
	TotalAlleles    *int
	End             *int
	MAPQ0Reads      *int
	NumberOfSamples *int
	MappingQuality  *float64
	Cigar           *string
	InDBSNP         *bool
	InHapmap2       *bool
	InHapmap3       *bool
	IsSomatic       *bool
	IsValidated     *bool
	In1000G         *bool
	BaseQuality     *float64
	StrandBias      *float64
}

// String provides a representation of the variant key: the fields Chrom, Pos, Ref and Alt
// compatible with fmt.Stringer
func (v *Variant) String() string {
	return fmt.Sprintf("Chromosome: %s Position: %d Reference: %s Alternative: %s", v.Chrom, v.Pos, v.Ref, v.Alt)
}

// InvalidLine represents a VCF line that could not be parsed. It encapsulates the problematic line with its corresponding error.
type InvalidLine struct {
	Line string
	Err  error
}

// ToChannel opens a file and puts all variants into an already initialized channel. Variants whose parsing fails go into a specific channel for failing variants
// Both channels are closed when the reader is fully scanned
func ToChannel(reader io.Reader, output chan<- *Variant, invalids chan<- InvalidLine) error {
	scanner := bufio.NewScanner(bufio.NewReader(reader))
	header, err := vcfHeader(scanner)
	if err != nil {
		return err
	}

	for scanner.Scan() {
		if isBlankOrHeaderLine(scanner.Text()) {
			continue
		}
		variants, err := parseVcfLine(scanner.Text(), header)
		if variants != nil && err == nil {
			for _, variant := range variants {
				output <- variant
			}
		} else if err != nil {
			invalids <- InvalidLine{scanner.Text(), err}
		}
	}

	close(output)
	close(invalids)

	return nil
}

// SampleIDs reads a vcf header from an io.Reader and returns a slice with all the sample IDs contained in that header
// If there are no samples on the header, a nil slice is returned
func SampleIDs(reader io.Reader) ([]string, error) {
	scanner := bufio.NewScanner(bufio.NewReader(reader))
	header, err := vcfHeader(scanner)
	if err != nil {
		return nil, err
	}
	if len(header) > 9 {
		return header[9:], nil
	}
	return nil, nil
}

func vcfHeader(scanner *bufio.Scanner) ([]string, error) {
	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), "#") && !strings.HasPrefix(scanner.Text(), "##") {
			return strings.Split(scanner.Text()[1:], "\t"), nil
		}
	}
	return nil, errors.New("vcf header not found on file")
}

func isBlankOrHeaderLine(line string) bool {
	return strings.HasPrefix(line, "#") || line == ""
}

type vcfLine struct {
	Chr, Pos, ID, Ref, Alt, Qual, Filter, Info string
	Format                                     []string
	Samples                                    []map[string]string
}

func parseVcfLine(line string, header []string) ([]*Variant, error) {
	vcfLine, err := splitVcfFields(line)
	if err != nil {
		return nil, errors.New("unable to parse apparently misformatted VCF line: " + line)
	}

	baseVariant := Variant{}
	baseVariant.Chrom = vcfLine.Chr
	pos, _ := strconv.Atoi(vcfLine.Pos)
	baseVariant.Pos = pos - 1 // converts variant to 0-based
	baseVariant.Ref = strings.ToUpper(vcfLine.Ref)
	baseVariant.Alt = strings.ToUpper(strings.Replace(vcfLine.Alt, ".", "", -1))

	baseVariant.ID = vcfLine.ID
	floatQuality, err := strconv.ParseFloat(vcfLine.Qual, 64)
	if err == nil {
		baseVariant.Qual = &floatQuality
	} else if vcfLine.Qual == "." {
		baseVariant.Qual = nil
	} else {
		baseVariant.Qual = nil
		log.Println("unable to parse quality as float, setting as nil")
	}
	baseVariant.Filter = vcfLine.Filter
	baseVariant.Samples = vcfLine.Samples
	baseVariant.Info = infoToMap(vcfLine.Info)

	alternatives := strings.Split(baseVariant.Alt, ",")

	info := splitMultipleAltInfos(baseVariant.Info, len(alternatives))

	result := make([]*Variant, 0, 64)
	for i, alternative := range alternatives {

		if baseVariant.Chrom != "" && baseVariant.Pos >= 0 && baseVariant.Ref != "" && alternative != "" {

			var altinfo map[string]interface{}
			if i >= len(info) {
				altinfo = info[0]
			} else {
				altinfo = info[i]
			}

			variant := &Variant{
				Chrom:   baseVariant.Chrom,
				Pos:     baseVariant.Pos,
				Ref:     baseVariant.Ref,
				Alt:     alternative,
				ID:      baseVariant.ID,
				Samples: baseVariant.Samples,
				Info:    altinfo,
				Qual:    baseVariant.Qual,
				Filter:  baseVariant.Filter,
			}
			buildInfoSubFields(variant)

			result = append(result, variant)

		} else {
			return nil, errors.New("error parsing variant: '" + line + "'")
		}
	}
	return result, nil
}

func splitVcfFields(line string) (ret *vcfLine, err error) {

	fields := strings.Split(line, "\t")

	if len(fields) < 8 {
		return nil, errors.New("wrong amount of columns: " + string(len(fields)))
	}
	ret = &vcfLine{}

	ret.Chr = fields[0]
	ret.Pos = fields[1]
	ret.ID = fields[2]
	ret.Ref = fields[3]
	ret.Alt = fields[4]
	ret.Qual = fields[5]
	ret.Filter = fields[6]
	ret.Info = fields[7]

	if len(fields) > 8 {
		samples := fields[9:len(fields)]
		ret.Samples = make([]map[string]string, len(fields)-9)
		ret.Format = strings.Split(fields[8], ":")
		for i, sample := range samples {
			ret.Samples[i] = parseSample(ret.Format, sample)
		}
	}

	return
}

func parseSample(format []string, unparsedSample string) map[string]string {
	sampleMapping := make(map[string]string)
	sampleFields := strings.Split(unparsedSample, ":")
	for i, field := range sampleFields {
		sampleMapping[format[i]] = field
	}
	return sampleMapping
}
<commit_msg>Documenting channel block<commit_after>package vcf

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"
)

// Variant is a struct representing the fields specified in the VCF 4.2 spec. It does not support structural variants. When the variant is generated through the API of the vcf package, the required fields are guaranteed to be valid, otherwise the parsing for the variant fails and is reported.
// Multiple alternatives are parsed as separated instances of the type Variant
// All other fields are optional and will not cause parsing fails if missing or non-conformant.
type Variant struct {
	// Required fields
	Chrom string
	Pos   int
	Ref   string
	Alt   string

	ID string
	// Qual is a pointer so that it can be set to nil when it is a dot '.'
	Qual   *float64
	Filter string
	// Info is a map containing all the keys present in the INFO field, with their corresponding value. For keys without corresponding values, the value is a `true` bool.
	// No attempt at parsing is made on this field, data is raw. The only exception is for multiple alternatives data. These are reported separately for each variant
	Info map[string]interface{}

	// Genotype fields for each sample
	Samples []map[string]string

	// Optional info fields. These are the reserved fields listed on the VCF 4.2 spec, session 1.4.1, number 8. The parsing is lenient, if the fields do not conform to the expected type listed here, they will be set to nil
	// The fields are meant as helpers for common scenarios, since the generic usage is covered by the Info map
	// Definitions used in the metadata section of the header are not used
	AncestralAllele *string
	Depth           *int
	AlleleFrequency *float64
	AlleleCount     *int
	TotalAlleles    *int
	End             *int
	MAPQ0Reads      *int
	NumberOfSamples *int
	MappingQuality  *float64
	Cigar           *string
	InDBSNP         *bool
	InHapmap2       *bool
	InHapmap3       *bool
	IsSomatic       *bool
	IsValidated     *bool
	In1000G         *bool
	BaseQuality     *float64
	StrandBias      *float64
}

// String provides a representation of the variant key: the fields Chrom, Pos, Ref and Alt
// compatible with fmt.Stringer
func (v *Variant) String() string {
	return fmt.Sprintf("Chromosome: %s Position: %d Reference: %s Alternative: %s", v.Chrom, v.Pos, v.Ref, v.Alt)
}

// InvalidLine represents a VCF line that could not be parsed. It encapsulates the problematic line with its corresponding error.
type InvalidLine struct {
	Line string
	Err  error
}

// ToChannel reads from an io.Reader and puts all variants into an already initialized channel.
// Variants whose parsing fails go into a specific channel for failing variants.
// If any of the two channels are full, ToChannel will block. The consumer must guarantee there is enough buffer space on the channels.
// Both channels are closed when the reader is fully scanned.
func ToChannel(reader io.Reader, output chan<- *Variant, invalids chan<- InvalidLine) error {
	scanner := bufio.NewScanner(bufio.NewReader(reader))
	header, err := vcfHeader(scanner)
	if err != nil {
		return err
	}

	for scanner.Scan() {
		if isBlankOrHeaderLine(scanner.Text()) {
			continue
		}
		variants, err := parseVcfLine(scanner.Text(), header)
		if variants != nil && err == nil {
			for _, variant := range variants {
				output <- variant
			}
		} else if err != nil {
			invalids <- InvalidLine{scanner.Text(), err}
		}
	}

	close(output)
	close(invalids)

	return nil
}

// SampleIDs reads a vcf header from an io.Reader and returns a slice with all the sample IDs contained in that header
// If there are no samples on the header, a nil slice is returned
func SampleIDs(reader io.Reader) ([]string, error) {
	scanner := bufio.NewScanner(bufio.NewReader(reader))
	header, err := vcfHeader(scanner)
	if err != nil {
		return nil, err
	}
	if len(header) > 9 {
		return header[9:], nil
	}
	return nil, nil
}

func vcfHeader(scanner *bufio.Scanner) ([]string, error) {
	for scanner.Scan() {
		if strings.HasPrefix(scanner.Text(), "#") && !strings.HasPrefix(scanner.Text(), "##") {
			return strings.Split(scanner.Text()[1:], "\t"), nil
		}
	}
	return nil, errors.New("vcf header not found on file")
}

func isBlankOrHeaderLine(line string) bool {
	return strings.HasPrefix(line, "#") || line == ""
}

type vcfLine struct {
	Chr, Pos, ID, Ref, Alt, Qual, Filter, Info string
	Format                                     []string
	Samples                                    []map[string]string
}

func parseVcfLine(line string, header []string) ([]*Variant, error) {
	vcfLine, err := splitVcfFields(line)
	if err != nil {
		return nil, errors.New("unable to parse apparently misformatted VCF line: " + line)
	}

	baseVariant := Variant{}
	baseVariant.Chrom = vcfLine.Chr
	pos, _ := strconv.Atoi(vcfLine.Pos)
	baseVariant.Pos = pos - 1 // converts variant to 0-based
	baseVariant.Ref = strings.ToUpper(vcfLine.Ref)
	baseVariant.Alt = strings.ToUpper(strings.Replace(vcfLine.Alt, ".", "", -1))

	baseVariant.ID = vcfLine.ID
	floatQuality, err := strconv.ParseFloat(vcfLine.Qual, 64)
	if err == nil {
		baseVariant.Qual = &floatQuality
	} else if vcfLine.Qual == "." {
		baseVariant.Qual = nil
	} else {
		baseVariant.Qual = nil
		log.Println("unable to parse quality as float, setting as nil")
	}
	baseVariant.Filter = vcfLine.Filter
	baseVariant.Samples = vcfLine.Samples
	baseVariant.Info = infoToMap(vcfLine.Info)

	alternatives := strings.Split(baseVariant.Alt, ",")

	info := splitMultipleAltInfos(baseVariant.Info, len(alternatives))

	result := make([]*Variant, 0, 64)
	for i, alternative := range alternatives {

		if baseVariant.Chrom != "" && baseVariant.Pos >= 0 && baseVariant.Ref != "" && alternative != "" {

			var altinfo map[string]interface{}
			if i >= len(info) {
				altinfo = info[0]
			} else {
				altinfo = info[i]
			}

			variant := &Variant{
				Chrom:   baseVariant.Chrom,
				Pos:     baseVariant.Pos,
				Ref:     baseVariant.Ref,
				Alt:     alternative,
				ID:      baseVariant.ID,
				Samples: baseVariant.Samples,
				Info:    altinfo,
				Qual:    baseVariant.Qual,
				Filter:  baseVariant.Filter,
			}
			buildInfoSubFields(variant)

			result = append(result, variant)

		} else {
			return nil, errors.New("error parsing variant: '" + line + "'")
		}
	}
	return result, nil
}

func splitVcfFields(line string) (ret *vcfLine, err error) {

	fields := strings.Split(line, "\t")

	if len(fields) < 8 {
		return nil, errors.New("wrong amount of columns: " + string(len(fields)))
	}
	ret = &vcfLine{}

	ret.Chr = fields[0]
	ret.Pos = fields[1]
	ret.ID = fields[2]
	ret.Ref = fields[3]
	ret.Alt = fields[4]
	ret.Qual = fields[5]
	ret.Filter = fields[6]
	ret.Info = fields[7]

	if len(fields) > 8 {
		samples := fields[9:len(fields)]
		ret.Samples = make([]map[string]string, len(fields)-9)
		ret.Format = strings.Split(fields[8], ":")
		for i, sample := range samples {
			ret.Samples[i] = parseSample(ret.Format, sample)
		}
	}

	return
}

func parseSample(format []string, unparsedSample string) map[string]string {
	sampleMapping := make(map[string]string)
	sampleFields := strings.Split(unparsedSample, ":")
	for i, field := range sampleFields {
		sampleMapping[format[i]] = field
	}
	return sampleMapping
}
<|endoftext|>


<commit_before>package vix

/*
#include "vix.h"
#include "helper.h"
*/
import "C"

import (
	"fmt"
	"runtime"
	"unsafe"
)

// VixPowerState
//
// These are the possible values reported for VIX_PROPERTY_VM_POWER_STATE
// property. These values are bitwise flags. The actual value returned for may
// be a bitwise OR of one more of these flags, along with other reserved values
// not documented here. They represent runtime information about the state of
// the virtual machine. To test the value of the property, use the
// Vix.GetProperties() function.
//
// Since VMware Server 1.0.
type VMPowerState int

const (
	// Indicates that VM.PowerOff() has been called, but the operation itself
	// has not completed.
	POWERSTATE_POWERING_OFF VMPowerState = C.VIX_POWERSTATE_POWERING_OFF

	// Indicates that the virtual machine is not running.
	POWERSTATE_POWERED_OFF VMPowerState = C.VIX_POWERSTATE_POWERED_OFF

	// Indicates that VM.PowerOn() has been called, but the operation itself
	// has not completed.
	POWERSTATE_POWERING_ON VMPowerState = C.VIX_POWERSTATE_POWERING_ON

	// Indicates that the virtual machine is running.
	POWERSTATE_POWERED_ON VMPowerState = C.VIX_POWERSTATE_POWERED_ON

	// Indicates that VM.Suspend() has been called, but the operation itself
	// has not completed.
	POWERSTATE_SUSPENDING VMPowerState = C.VIX_POWERSTATE_SUSPENDING

	// Indicates that the virtual machine is suspended. Use VM.PowerOn() to
	// resume the virtual machine.
	POWERSTATE_SUSPENDED VMPowerState = C.VIX_POWERSTATE_SUSPENDED

	// Indicates that the virtual machine is running and the VMware Tools
	// suite is active. See also the VixToolsState property.
	POWERSTATE_TOOLS_RUNNING VMPowerState = C.VIX_POWERSTATE_TOOLS_RUNNING

	// Indicates that VM.Reset() has been called, but the operation itself
	// has not completed.
	POWERSTATE_RESETTING VMPowerState = C.VIX_POWERSTATE_RESETTING

	// Indicates that a virtual machine state change is blocked, waiting for
	// user interaction.
	POWERSTATE_BLOCKED_ON_MSG VMPowerState = C.VIX_POWERSTATE_BLOCKED_ON_MSG
)

// VixFindItemType
//
// These are the types of searches you can do with Host.FindItems().
//
// Since VMware Server 1.0.
type SearchType int

const (
	// Finds all virtual machines currently running on the host.
	FIND_RUNNING_VMS SearchType = C.VIX_FIND_RUNNING_VMS

	// Finds all virtual machines registered on the host.
	// This search applies only to platform products that maintain a virtual
	// machine registry,
	// such as ESX/ESXi and VMware Server, but not Workstation or Player.
	FIND_REGISTERED_VMS SearchType = C.VIX_FIND_REGISTERED_VMS
)

// VixToolsState
//
// These are the possible values reported for VIX_PROPERTY_VM_TOOLS_STATE.
// They represent runtime information about the VMware Tools suite in the guest
// operating system.
// To test the value of the property, use the Vix.GetProperties() function.
//
// Since VMware Server 1.0.
type GuestToolsState int

const (
	// Indicates that Vix is unable to determine the VMware Tools status.
	TOOLSSTATE_UNKNOWN GuestToolsState = C.VIX_TOOLSSTATE_UNKNOWN

	// Indicates that VMware Tools is running in the guest operating system.
	TOOLSSTATE_RUNNING GuestToolsState = C.VIX_TOOLSSTATE_RUNNING

	// Indicates that VMware Tools is not installed in the guest operating system.
	TOOLSSTATE_NOT_INSTALLED GuestToolsState = C.VIX_TOOLSSTATE_NOT_INSTALLED
)

// Service Provider
type Provider int

const (
	// vCenter Server, ESX/ESXi hosts, and VMware Server 2.0
	VMWARE_VI_SERVER Provider = C.VIX_SERVICEPROVIDER_VMWARE_VI_SERVER

	// VMware Workstation
	VMWARE_WORKSTATION Provider = C.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION

	// VMware Workstation (shared mode)
	VMWARE_WORKSTATION_SHARED Provider = C.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION_SHARED

	// With VMware Player
	VMWARE_PLAYER Provider = C.VIX_SERVICEPROVIDER_VMWARE_PLAYER

	// VMware Server 1.0.x
	VMWARE_SERVER Provider = C.VIX_SERVICEPROVIDER_VMWARE_SERVER
)

type EventType int

const (
	JOB_COMPLETED EventType = C.VIX_EVENTTYPE_JOB_COMPLETED
	JOB_PROGRESS  EventType = C.VIX_EVENTTYPE_JOB_PROGRESS
	FIND_ITEM     EventType = C.VIX_EVENTTYPE_FIND_ITEM
)

type HostOption int

const (
	HOST_OPTIONS_NONE = 0x0
	VERIFY_SSL_CERT   = C.VIX_HOSTOPTION_VERIFY_SSL_CERT
)

type GuestLoginOption int

const (
	LOGIN_IN_GUEST_NONE                            GuestLoginOption = 0x0
	LOGIN_IN_GUEST_REQUIRE_INTERACTIVE_ENVIRONMENT GuestLoginOption = C.VIX_LOGIN_IN_GUEST_REQUIRE_INTERACTIVE_ENVIRONMENT
)

type GuestVarType int

const (
	VM_GUEST_VARIABLE          GuestVarType = C.VIX_VM_GUEST_VARIABLE
	VM_CONFIG_RUNTIME_ONLY     GuestVarType = C.VIX_VM_CONFIG_RUNTIME_ONLY
	GUEST_ENVIRONMENT_VARIABLE GuestVarType = C.VIX_GUEST_ENVIRONMENT_VARIABLE
)

type SharedFolderOption int

const (
	SHAREDFOLDER_WRITE_ACCESS = C.VIX_SHAREDFOLDER_WRITE_ACCESS
)

type CloneType int

const (
	CLONETYPE_FULL   CloneType = C.VIX_CLONETYPE_FULL
	CLONETYPE_LINKED CloneType = C.VIX_CLONETYPE_LINKED
)

type CreateSnapshotOption int

const (
	SNAPSHOT_INCLUDE_MEMORY CreateSnapshotOption = C.VIX_SNAPSHOT_INCLUDE_MEMORY
)

type VmDeleteOption int

const (
	VMDELETE_DISK_FILES VmDeleteOption = C.VIX_VMDELETE_DISK_FILES
)

type VMPowerOption int

const (
	VMPOWEROP_NORMAL                    VMPowerOption = C.VIX_VMPOWEROP_NORMAL
	VMPOWEROP_FROM_GUEST                VMPowerOption = C.VIX_VMPOWEROP_FROM_GUEST
	VMPOWEROP_SUPPRESS_SNAPSHOT_POWERON VMPowerOption = C.VIX_VMPOWEROP_SUPPRESS_SNAPSHOT_POWERON
	VMPOWEROP_LAUNCH_GUI                VMPowerOption = C.VIX_VMPOWEROP_LAUNCH_GUI
	VMPOWEROP_START_VM_PAUSED           VMPowerOption = C.VIX_VMPOWEROP_START_VM_PAUSED
)

type RunProgramOption int

const (
	RUNPROGRAM_WAIT               RunProgramOption = 0x0
	RUNPROGRAM_RETURN_IMMEDIATELY RunProgramOption = C.VIX_RUNPROGRAM_RETURN_IMMEDIATELY
	RUNPROGRAM_ACTIVATE_WINDOW    RunProgramOption = C.VIX_RUNPROGRAM_ACTIVATE_WINDOW
)

type InstallToolsOption int

const (
	INSTALLTOOLS_MOUNT_TOOLS_INSTALLER InstallToolsOption = C.VIX_INSTALLTOOLS_MOUNT_TOOLS_INSTALLER
	INSTALLTOOLS_AUTO_UPGRADE          InstallToolsOption = C.VIX_INSTALLTOOLS_AUTO_UPGRADE
	INSTALLTOOLS_RETURN_IMMEDIATELY    InstallToolsOption = C.VIX_INSTALLTOOLS_RETURN_IMMEDIATELY
)

type RemoveSnapshotOption int

const (
	SNAPSHOT_REMOVE_NONE     RemoveSnapshotOption = 0x0
	SNAPSHOT_REMOVE_CHILDREN RemoveSnapshotOption = C.VIX_SNAPSHOT_REMOVE_CHILDREN
)

type FileAttr int

const (
	FILE_ATTRIBUTES_DIRECTORY FileAttr = C.VIX_FILE_ATTRIBUTES_DIRECTORY
	FILE_ATTRIBUTES_SYMLINK   FileAttr = C.VIX_FILE_ATTRIBUTES_SYMLINK
)

// Configuration struct for the Connect function
type ConnectConfig struct {
	// Hostname varies by product platform. With vCenter Server, ESX/ESXi hosts,
	// VMware Workstation (shared mode) and VMware Server 2.0,
	// use a URL of the form "https://<hostName>:<port>/sdk"
	// where <hostName> is either the DNS name or IP address.
	// If missing, <port> may default to 443 (see Remarks below).
	//
	// In VIX API 1.10 and later, you can omit "https://" and "/sdk" specifying
	// just the DNS name or IP address.
	// Credentials are required even for connections made locally.
	// With Workstation, use nil to connect to the local host.
	// With VMware Server 1.0.x, use the DNS name or IP address for remote
	// connections, or the same as Workstation for local connections.
	Hostname string

	// TCP/IP port on the remote host.
	// With VMware Workstation and VMware Player, let it empty for localhost.
	// With ESX/ESXi hosts, VMware Workstation (shared mode) and VMware Server 2.0
	// you specify port number within the hostName parameter, so this parameter is
	// ignored (see Connect Remarks below).
	Port uint

	// Username for authentication on the remote machine.
	// With VMware Workstation, VMware Player, and VMware Server 1.0.x,
	// let it empty to authenticate as the current user on localhost.
	// With vCenter Server, ESX/ESXi hosts, VMware Workstation (shared mode)
	// and VMware Server 2.0, you must use a valid login.
	Username string

	// Password for authentication on the remote machine.
	// With VMware Workstation, VMware Player, and VMware Server 1.0.x,
	// let it empty to authenticate as the current user on localhost.
	// With ESX/ESXi, VMware Workstation (shared mode) and VMware Server 2.0, you
	// must use a valid password.
	Password string

	// Provider is the VMware product you would like to connect to:
	// * With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, use
	//   VMWARE_VI_SERVER.
	// * With VMware Workstation, use VMWARE_WORKSTATION.
	// * With VMware Workstation (shared mode), use VMWARE_WORKSTATION_SHARED.
	// * With VMware Player, use VMWARE_PLAYER.
	// * With VMware Server 1.0.x, use VMWARE_SERVER.
	Provider Provider

	// Bitwised option parameter.
	Options HostOption
}

// Connects to a Provider
//
// Parameters:
//
//  config: See type ConnectConfig documentation for details
//
// Remarks:
//   * To specify the local host (where the API client runs) with VMware
//     Workstation and VMware Player, pass empty values for the hostname, port,
//     login, and password parameters or just don't set them.
//
//   * With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, the URL for
//     the hostname argument may specify the port.
//     Otherwise a HTTPS connection is attempted on port 443. HTTPS is strongly
//     recommended.
//     Port numbers are set during installation of Server 2.0. The installer's
//     default HTTP and HTTPS values are 8222 and 8333 for Server on Windows, or
//     (if not already in use) 80 and 443 for Server on Linux, and 902 for the
//     automation socket, authd. If connecting to a virtual machine through a
//     firewall, port 902 and the communicating port must be opened to allow
//     guest operations.
//
//   * If a VMware ESX host is being managed by a VMware VCenter Server, you
//     should call VixHost_Connect with the hostname or IP address of the VCenter
//     server, not the ESX host.
//     Connecting directly to an ESX host while bypassing its VCenter Server can
//     cause state inconsistency.
//
//   * On Windows, this function should not be called multiple times with
//     different service providers in the same process; doing so will result in
//     a VIX_E_WRAPPER_MULTIPLE_SERVICEPROVIDERS error.
//     A single client process can connect to multiple hosts as long as it
//     connects using the same service provider type.
//
//   * To enable SSL certificate verification, set the value of the options
//     parameter to include the bit flag specified by VERIFY_SSL_CERT.
//     This option can also be set in the VMware config file by assigning
//     vix.enableSslCertificateCheck as TRUE or FALSE.
//
//     The vix.sslCertificateFile config option specifies the path to a file
//     containing CA certificates in PEM format.
//
//     The vix.sslCertificateDirectory config option can specify a directory
//     containing files that each contain a CA certificate.
//     Upon encountering a SSL validation error, the host handle is not created
//     with a resulting error code of E_NET_HTTP_SSL_SECURITY.
//
//   * With VMware vCenter Server and ESX/ESXi 4.0 hosts, an existing VI API
//     session can be used instead of the username/password pair to authenticate
//     when connecting. To use an existing VI API session, a VI "clone ticket"
//     is required; call the VI API AcquireCloneTicket() method of the
//     SessionManager object to get this ticket.
//     Using the ticket string returned by this method, call vix.Connect()
//     with "" as the 'username' and the ticket as the 'password'.
//
// Since VMware Server 1.0
func Connect(config ConnectConfig) (*Host, error) {
	var jobHandle C.VixHandle = C.VIX_INVALID_HANDLE
	var hostHandle C.VixHandle = C.VIX_INVALID_HANDLE
	var err C.VixError = C.VIX_OK

	chostname := C.CString(config.Hostname)
	cusername := C.CString(config.Username)
	cpassword := C.CString(config.Password)
	defer C.free(unsafe.Pointer(chostname))
	defer C.free(unsafe.Pointer(cusername))
	defer C.free(unsafe.Pointer(cpassword))

	jobHandle = C.VixHost_Connect(C.VIX_API_VERSION,
		C.VixServiceProvider(config.Provider),
		chostname,
		C.int(config.Port),
		cusername,
		cpassword,
		C.VixHostOptions(config.Options),
		C.VIX_INVALID_HANDLE, // propertyListHandle
		nil,                  // callbackProc
		nil)                  // clientData

	err = C.get_vix_handle(jobHandle,
		C.VIX_PROPERTY_JOB_RESULT_HANDLE,
		&hostHandle,
		C.VIX_PROPERTY_NONE)

	defer C.Vix_ReleaseHandle(jobHandle)

	if C.VIX_OK != err {
		return nil, &VixError{
			Operation: "vix.Connect",
			Code:      int(err & 0xFFFF),
			Text:      C.GoString(C.Vix_GetErrorText(err, nil)),
		}
	}

	host := &Host{
		handle:   hostHandle,
		Provider: config.Provider,
	}

	runtime.SetFinalizer(host, cleanupHost)

	return host, nil
}

// Private function to clean up host handle
func cleanupHost(host *Host) {
	if host.handle != C.VIX_INVALID_HANDLE {
		host.Disconnect()
	}
}

// GoVix Error
type VixError struct {
	// The GoVix operation involved at the time of the error
	Operation string
	// Error code
	Code int
	// Description of the erro
	Text string
}

// Returns a description of the error along with its code and operation
func (e *VixError) Error() string {
	return fmt.Sprintf("VIX Error: %s, code: %d, operation: %s", e.Text, e.Code, e.Operation)
}
<commit_msg>Adds a new constant to have the choice of not removing vm disk files upon virtual machine removal<commit_after>package vix

/*
#include "vix.h"
#include "helper.h"
*/
import "C"

import (
	"fmt"
	"runtime"
	"unsafe"
)

// VixPowerState
//
// These are the possible values reported for VIX_PROPERTY_VM_POWER_STATE
// property. These values are bitwise flags. The actual value returned for may
// be a bitwise OR of one more of these flags, along with other reserved values
// not documented here. They represent runtime information about the state of
// the virtual machine. To test the value of the property, use the
// Vix.GetProperties() function.
//
// Since VMware Server 1.0.
type VMPowerState int

const (
	// Indicates that VM.PowerOff() has been called, but the operation itself
	// has not completed.
	POWERSTATE_POWERING_OFF VMPowerState = C.VIX_POWERSTATE_POWERING_OFF

	// Indicates that the virtual machine is not running.
	POWERSTATE_POWERED_OFF VMPowerState = C.VIX_POWERSTATE_POWERED_OFF

	// Indicates that VM.PowerOn() has been called, but the operation itself
	// has not completed.
	POWERSTATE_POWERING_ON VMPowerState = C.VIX_POWERSTATE_POWERING_ON

	// Indicates that the virtual machine is running.
	POWERSTATE_POWERED_ON VMPowerState = C.VIX_POWERSTATE_POWERED_ON

	// Indicates that VM.Suspend() has been called, but the operation itself
	// has not completed.
	POWERSTATE_SUSPENDING VMPowerState = C.VIX_POWERSTATE_SUSPENDING

	// Indicates that the virtual machine is suspended. Use VM.PowerOn() to
	// resume the virtual machine.
	POWERSTATE_SUSPENDED VMPowerState = C.VIX_POWERSTATE_SUSPENDED

	// Indicates that the virtual machine is running and the VMware Tools
	// suite is active. See also the VixToolsState property.
	POWERSTATE_TOOLS_RUNNING VMPowerState = C.VIX_POWERSTATE_TOOLS_RUNNING

	// Indicates that VM.Reset() has been called, but the operation itself
	// has not completed.
	POWERSTATE_RESETTING VMPowerState = C.VIX_POWERSTATE_RESETTING

	// Indicates that a virtual machine state change is blocked, waiting for
	// user interaction.
	POWERSTATE_BLOCKED_ON_MSG VMPowerState = C.VIX_POWERSTATE_BLOCKED_ON_MSG
)

// VixFindItemType
//
// These are the types of searches you can do with Host.FindItems().
//
// Since VMware Server 1.0.
type SearchType int

const (
	// Finds all virtual machines currently running on the host.
	FIND_RUNNING_VMS SearchType = C.VIX_FIND_RUNNING_VMS

	// Finds all virtual machines registered on the host.
	// This search applies only to platform products that maintain a virtual
	// machine registry,
	// such as ESX/ESXi and VMware Server, but not Workstation or Player.
	FIND_REGISTERED_VMS SearchType = C.VIX_FIND_REGISTERED_VMS
)

// VixToolsState
//
// These are the possible values reported for VIX_PROPERTY_VM_TOOLS_STATE.
// They represent runtime information about the VMware Tools suite in the guest
// operating system.
// To test the value of the property, use the Vix.GetProperties() function.
//
// Since VMware Server 1.0.
type GuestToolsState int

const (
	// Indicates that Vix is unable to determine the VMware Tools status.
	TOOLSSTATE_UNKNOWN GuestToolsState = C.VIX_TOOLSSTATE_UNKNOWN

	// Indicates that VMware Tools is running in the guest operating system.
	TOOLSSTATE_RUNNING GuestToolsState = C.VIX_TOOLSSTATE_RUNNING

	// Indicates that VMware Tools is not installed in the guest operating system.
	TOOLSSTATE_NOT_INSTALLED GuestToolsState = C.VIX_TOOLSSTATE_NOT_INSTALLED
)

// Service Provider
type Provider int

const (
	// vCenter Server, ESX/ESXi hosts, and VMware Server 2.0
	VMWARE_VI_SERVER Provider = C.VIX_SERVICEPROVIDER_VMWARE_VI_SERVER

	// VMware Workstation
	VMWARE_WORKSTATION Provider = C.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION

	// VMware Workstation (shared mode)
	VMWARE_WORKSTATION_SHARED Provider = C.VIX_SERVICEPROVIDER_VMWARE_WORKSTATION_SHARED

	// With VMware Player
	VMWARE_PLAYER Provider = C.VIX_SERVICEPROVIDER_VMWARE_PLAYER

	// VMware Server 1.0.x
	VMWARE_SERVER Provider = C.VIX_SERVICEPROVIDER_VMWARE_SERVER
)

type EventType int

const (
	JOB_COMPLETED EventType = C.VIX_EVENTTYPE_JOB_COMPLETED
	JOB_PROGRESS  EventType = C.VIX_EVENTTYPE_JOB_PROGRESS
	FIND_ITEM     EventType = C.VIX_EVENTTYPE_FIND_ITEM
)

type HostOption int

const (
	HOST_OPTIONS_NONE = 0x0
	VERIFY_SSL_CERT   = C.VIX_HOSTOPTION_VERIFY_SSL_CERT
)

type GuestLoginOption int

const (
	LOGIN_IN_GUEST_NONE                            GuestLoginOption = 0x0
	LOGIN_IN_GUEST_REQUIRE_INTERACTIVE_ENVIRONMENT GuestLoginOption = C.VIX_LOGIN_IN_GUEST_REQUIRE_INTERACTIVE_ENVIRONMENT
)

type GuestVarType int

const (
	VM_GUEST_VARIABLE          GuestVarType = C.VIX_VM_GUEST_VARIABLE
	VM_CONFIG_RUNTIME_ONLY     GuestVarType = C.VIX_VM_CONFIG_RUNTIME_ONLY
	GUEST_ENVIRONMENT_VARIABLE GuestVarType = C.VIX_GUEST_ENVIRONMENT_VARIABLE
)

type SharedFolderOption int

const (
	SHAREDFOLDER_WRITE_ACCESS = C.VIX_SHAREDFOLDER_WRITE_ACCESS
)

type CloneType int

const (
	CLONETYPE_FULL   CloneType = C.VIX_CLONETYPE_FULL
	CLONETYPE_LINKED CloneType = C.VIX_CLONETYPE_LINKED
)

type CreateSnapshotOption int

const (
	SNAPSHOT_INCLUDE_MEMORY CreateSnapshotOption = C.VIX_SNAPSHOT_INCLUDE_MEMORY
)

type VmDeleteOption int

const (
	VMDELETE_KEEP_FILES VmDeleteOption = 0x0
	VMDELETE_DISK_FILES VmDeleteOption = C.VIX_VMDELETE_DISK_FILES
)

type VMPowerOption int

const (
	VMPOWEROP_NORMAL                    VMPowerOption = C.VIX_VMPOWEROP_NORMAL
	VMPOWEROP_FROM_GUEST                VMPowerOption = C.VIX_VMPOWEROP_FROM_GUEST
	VMPOWEROP_SUPPRESS_SNAPSHOT_POWERON VMPowerOption = C.VIX_VMPOWEROP_SUPPRESS_SNAPSHOT_POWERON
	VMPOWEROP_LAUNCH_GUI                VMPowerOption = C.VIX_VMPOWEROP_LAUNCH_GUI
	VMPOWEROP_START_VM_PAUSED           VMPowerOption = C.VIX_VMPOWEROP_START_VM_PAUSED
)

type RunProgramOption int

const (
	RUNPROGRAM_WAIT               RunProgramOption = 0x0
	RUNPROGRAM_RETURN_IMMEDIATELY RunProgramOption = C.VIX_RUNPROGRAM_RETURN_IMMEDIATELY
	RUNPROGRAM_ACTIVATE_WINDOW    RunProgramOption = C.VIX_RUNPROGRAM_ACTIVATE_WINDOW
)

type InstallToolsOption int

const (
	INSTALLTOOLS_MOUNT_TOOLS_INSTALLER InstallToolsOption = C.VIX_INSTALLTOOLS_MOUNT_TOOLS_INSTALLER
	INSTALLTOOLS_AUTO_UPGRADE          InstallToolsOption = C.VIX_INSTALLTOOLS_AUTO_UPGRADE
	INSTALLTOOLS_RETURN_IMMEDIATELY    InstallToolsOption = C.VIX_INSTALLTOOLS_RETURN_IMMEDIATELY
)

type RemoveSnapshotOption int

const (
	SNAPSHOT_REMOVE_NONE     RemoveSnapshotOption = 0x0
	SNAPSHOT_REMOVE_CHILDREN RemoveSnapshotOption = C.VIX_SNAPSHOT_REMOVE_CHILDREN
)

type FileAttr int

const (
	FILE_ATTRIBUTES_DIRECTORY FileAttr = C.VIX_FILE_ATTRIBUTES_DIRECTORY
	FILE_ATTRIBUTES_SYMLINK   FileAttr = C.VIX_FILE_ATTRIBUTES_SYMLINK
)

// Configuration struct for the Connect function
type ConnectConfig struct {
	// Hostname varies by product platform. With vCenter Server, ESX/ESXi hosts,
	// VMware Workstation (shared mode) and VMware Server 2.0,
	// use a URL of the form "https://<hostName>:<port>/sdk"
	// where <hostName> is either the DNS name or IP address.
	// If missing, <port> may default to 443 (see Remarks below).
	//
	// In VIX API 1.10 and later, you can omit "https://" and "/sdk" specifying
	// just the DNS name or IP address.
	// Credentials are required even for connections made locally.
	// With Workstation, use nil to connect to the local host.
	// With VMware Server 1.0.x, use the DNS name or IP address for remote
	// connections, or the same as Workstation for local connections.
	Hostname string

	// TCP/IP port on the remote host.
	// With VMware Workstation and VMware Player, let it empty for localhost.
	// With ESX/ESXi hosts, VMware Workstation (shared mode) and VMware Server 2.0
	// you specify port number within the hostName parameter, so this parameter is
	// ignored (see Connect Remarks below).
	Port uint

	// Username for authentication on the remote machine.
	// With VMware Workstation, VMware Player, and VMware Server 1.0.x,
	// let it empty to authenticate as the current user on localhost.
	// With vCenter Server, ESX/ESXi hosts, VMware Workstation (shared mode)
	// and VMware Server 2.0, you must use a valid login.
	Username string

	// Password for authentication on the remote machine.
	// With VMware Workstation, VMware Player, and VMware Server 1.0.x,
	// let it empty to authenticate as the current user on localhost.
	// With ESX/ESXi, VMware Workstation (shared mode) and VMware Server 2.0, you
	// must use a valid password.
	Password string

	// Provider is the VMware product you would like to connect to:
	// * With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, use
	//   VMWARE_VI_SERVER.
	// * With VMware Workstation, use VMWARE_WORKSTATION.
	// * With VMware Workstation (shared mode), use VMWARE_WORKSTATION_SHARED.
	// * With VMware Player, use VMWARE_PLAYER.
	// * With VMware Server 1.0.x, use VMWARE_SERVER.
	Provider Provider

	// Bitwised option parameter.
	Options HostOption
}

// Connects to a Provider
//
// Parameters:
//
//  config: See type ConnectConfig documentation for details
//
// Remarks:
//   * To specify the local host (where the API client runs) with VMware
//     Workstation and VMware Player, pass empty values for the hostname, port,
//     login, and password parameters or just don't set them.
//
//   * With vCenter Server, ESX/ESXi hosts, and VMware Server 2.0, the URL for
//     the hostname argument may specify the port.
//     Otherwise a HTTPS connection is attempted on port 443. HTTPS is strongly
//     recommended.
//     Port numbers are set during installation of Server 2.0. The installer's
//     default HTTP and HTTPS values are 8222 and 8333 for Server on Windows, or
//     (if not already in use) 80 and 443 for Server on Linux, and 902 for the
//     automation socket, authd. If connecting to a virtual machine through a
//     firewall, port 902 and the communicating port must be opened to allow
//     guest operations.
//
//   * If a VMware ESX host is being managed by a VMware VCenter Server, you
//     should call VixHost_Connect with the hostname or IP address of the VCenter
//     server, not the ESX host.
//     Connecting directly to an ESX host while bypassing its VCenter Server can
//     cause state inconsistency.
//
//   * On Windows, this function should not be called multiple times with
//     different service providers in the same process; doing so will result in
//     a VIX_E_WRAPPER_MULTIPLE_SERVICEPROVIDERS error.
//     A single client process can connect to multiple hosts as long as it
//     connects using the same service provider type.
//
//   * To enable SSL certificate verification, set the value of the options
//     parameter to include the bit flag specified by VERIFY_SSL_CERT.
//     This option can also be set in the VMware config file by assigning
//     vix.enableSslCertificateCheck as TRUE or FALSE.
//
//     The vix.sslCertificateFile config option specifies the path to a file
//     containing CA certificates in PEM format.
//
//     The vix.sslCertificateDirectory config option can specify a directory
//     containing files that each contain a CA certificate.
//     Upon encountering a SSL validation error, the host handle is not created
//     with a resulting error code of E_NET_HTTP_SSL_SECURITY.
//
//   * With VMware vCenter Server and ESX/ESXi 4.0 hosts, an existing VI API
//     session can be used instead of the username/password pair to authenticate
//     when connecting. To use an existing VI API session, a VI "clone ticket"
//     is required; call the VI API AcquireCloneTicket() method of the
//     SessionManager object to get this ticket.
//     Using the ticket string returned by this method, call vix.Connect()
//     with "" as the 'username' and the ticket as the 'password'.
//
// Since VMware Server 1.0
func Connect(config ConnectConfig) (*Host, error) {
	var jobHandle C.VixHandle = C.VIX_INVALID_HANDLE
	var hostHandle C.VixHandle = C.VIX_INVALID_HANDLE
	var err C.VixError = C.VIX_OK

	chostname := C.CString(config.Hostname)
	cusername := C.CString(config.Username)
	cpassword := C.CString(config.Password)
	defer C.free(unsafe.Pointer(chostname))
	defer C.free(unsafe.Pointer(cusername))
	defer C.free(unsafe.Pointer(cpassword))

	jobHandle = C.VixHost_Connect(C.VIX_API_VERSION,
		C.VixServiceProvider(config.Provider),
		chostname,
		C.int(config.Port),
		cusername,
		cpassword,
		C.VixHostOptions(config.Options),
		C.VIX_INVALID_HANDLE, // propertyListHandle
		nil,                  // callbackProc
		nil)                  // clientData

	err = C.get_vix_handle(jobHandle,
		C.VIX_PROPERTY_JOB_RESULT_HANDLE,
		&hostHandle,
		C.VIX_PROPERTY_NONE)

	defer C.Vix_ReleaseHandle(jobHandle)

	if C.VIX_OK != err {
		return nil, &VixError{
			Operation: "vix.Connect",
			Code:      int(err & 0xFFFF),
			Text:      C.GoString(C.Vix_GetErrorText(err, nil)),
		}
	}

	host := &Host{
		handle:   hostHandle,
		Provider: config.Provider,
	}

	runtime.SetFinalizer(host, cleanupHost)

	return host, nil
}

// Private function to clean up host handle
func cleanupHost(host *Host) {
	if host.handle != C.VIX_INVALID_HANDLE {
		host.Disconnect()
	}
}

// GoVix Error
type VixError struct {
	// The GoVix operation involved at the time of the error
	Operation string
	// Error code
	Code int
	// Description of the erro
	Text string
}

// Returns a description of the error along with its code and operation
func (e *VixError) Error() string {
	return fmt.Sprintf("VIX Error: %s, code: %d, operation: %s", e.Text, e.Code, e.Operation)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"time"
)

var idChars = []rune("0123456789abcdef")
var idLength = 32

func randId() string {
	b := make([]rune, idLength)
	for i := range b {
		b[i] = idChars[rand.Intn(len(idChars))]
	}
	return string(b)
}

type Clients struct {
	channelById    map[string]chan string
	newClients     chan newClient
	defunctClients chan string
}

type newClient struct {
	id      string
	channel chan string
}

func (c *Clients) Start() {
	for {
		select {
		case client := <-c.newClients:
			c.channelById[client.id] = client.channel
			fmt.Printf("connected %s %d\n", client.id, time.Now().Unix())
		case id := <-c.defunctClients:
			delete(c.channelById, id)
			fmt.Printf("disconnected %s %d\n", id, time.Now().Unix())
		}
	}
}

func (b *Clients) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	f, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
		return
	}

	id := randId()
	messageChan := make(chan string)
	b.newClients <- newClient{id, messageChan}

	notify := w.(http.CloseNotifier).CloseNotify()
	go func() {
		<-notify
		b.defunctClients <- id
	}()

	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	// Tell nginx to not buffer. Without this it may take up to a minute
	// for events to arrive at the client.
	w.Header().Set("X-Accel-Buffering", "no")

	for {
		msg := <-messageChan
		fmt.Fprintf(w, "data: %s\n\n", msg)
		f.Flush()
	}
}

var pattern = regexp.MustCompile(`(\S+) (\S+) (\S+)`)

func ReadCommands(clients *Clients) {
	reader := bufio.NewReader(os.Stdin)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			panic(err)
		}
		parts := pattern.FindStringSubmatch(line)
		if len(parts) == 0 {
			fmt.Fprintf(os.Stderr, "Invalid command format. Expected 'command id params'.\n")
			continue
		}
		command := parts[1]
		id := parts[2]
		params := parts[3]
		switch command {
		case "send":
			if id == "world" {
				for _, s := range clients.channelById {
					s <- params
				}
			} else {
				clients.channelById[id] <- params
			}
		default:
			fmt.Fprintf(os.Stderr, "Invalid command "+command+"\n")
		}
	}
}

func HandleCall(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading body.", http.StatusInternalServerError)
		fmt.Fprintf(os.Stderr, err.Error())
		return
	}
	fmt.Printf("call %s\n", body)
}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())

	clients := &Clients{
		make(map[string]chan string),
		make(chan newClient),
		make(chan string),
	}

	go clients.Start()
	http.Handle("/events/", clients)

	go ReadCommands(clients)

	http.Handle("/call", http.HandlerFunc(HandleCall))
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "index.html")
	})
	panic(http.ListenAndServe(":8000", nil))
}
<commit_msg>Flush headers to avoid browser confusion<commit_after>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"time"
)

var idChars = []rune("0123456789abcdef")
var idLength = 32

func randId() string {
	b := make([]rune, idLength)
	for i := range b {
		b[i] = idChars[rand.Intn(len(idChars))]
	}
	return string(b)
}

type Clients struct {
	channelById    map[string]chan string
	newClients     chan newClient
	defunctClients chan string
}

type newClient struct {
	id      string
	channel chan string
}

func (c *Clients) Start() {
	for {
		select {
		case client := <-c.newClients:
			c.channelById[client.id] = client.channel
			fmt.Printf("connected %s %d\n", client.id, time.Now().Unix())
		case id := <-c.defunctClients:
			delete(c.channelById, id)
			fmt.Printf("disconnected %s %d\n", id, time.Now().Unix())
		}
	}
}

func (b *Clients) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	f, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
		return
	}

	id := randId()
	messageChan := make(chan string)
	b.newClients <- newClient{id, messageChan}

	notify := w.(http.CloseNotifier).CloseNotify()
	go func() {
		<-notify
		b.defunctClients <- id
	}()

	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	// Tell nginx to not buffer. Without this it may take up to a minute
	// for events to arrive at the client.
	w.Header().Set("X-Accel-Buffering", "no")
	fmt.Fprintf(w, "\n\n")
	f.Flush()

	for {
		msg := <-messageChan
		fmt.Fprintf(w, "data: %s\n\n", msg)
		f.Flush()
	}
}

var pattern = regexp.MustCompile(`(\S+) (\S+) (\S+)`)

func ReadCommands(clients *Clients) {
	reader := bufio.NewReader(os.Stdin)
	for {
		line, err := reader.ReadString('\n')
		if err != nil {
			panic(err)
		}
		parts := pattern.FindStringSubmatch(line)
		if len(parts) == 0 {
			fmt.Fprintf(os.Stderr, "Invalid command format. Expected 'command id params'.\n")
			continue
		}
		command := parts[1]
		id := parts[2]
		params := parts[3]
		switch command {
		case "send":
			if id == "world" {
				for _, s := range clients.channelById {
					s <- params
				}
			} else {
				clients.channelById[id] <- params
			}
		default:
			fmt.Fprintf(os.Stderr, "Invalid command "+command+"\n")
		}
	}
}

func HandleCall(w http.ResponseWriter, r *http.Request) {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Error reading body.", http.StatusInternalServerError)
		fmt.Fprintf(os.Stderr, err.Error())
		return
	}
	fmt.Printf("call %s\n", body)
}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())

	clients := &Clients{
		make(map[string]chan string),
		make(chan newClient),
		make(chan string),
	}

	go clients.Start()
	http.Handle("/events/", clients)

	go ReadCommands(clients)

	http.Handle("/call", http.HandlerFunc(HandleCall))
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "index.html")
	})
	panic(http.ListenAndServe(":8000", nil))
}
<|endoftext|>


<commit_before>package web

import (
    "bytes"
    "fmt"
    "http"
    "io/ioutil"
    "log"
    "os"
    "reflect"
    "regexp"
    "strings"
    "template"
)

var compiledRoutes = map[*regexp.Regexp]*reflect.FuncValue{}

func compileRoutes(urls map[string]interface{}) {
    for r, f := range urls {
        regex, err := regexp.Compile(r)
        if err != nil {
            println("Error in route")
        }
        fv := reflect.NewValue(f).(*reflect.FuncValue)
        compiledRoutes[regex] = fv
    }
}

func routeHandler(c *http.Conn, req *http.Request) {
    println(req.RawURL)

    //try to serve a static file
    if strings.HasPrefix(req.RawURL, "/static/") {
        staticFile := req.RawURL[8:]
        if len(staticFile) > 0 {
            http.ServeFile(c, req, "static/"+staticFile)
        }
    }

    var route string = req.RawURL
    for r, fv := range compiledRoutes {
        if !r.MatchString(route) {
            continue
        }
        match := r.MatchStrings(route)
        if len(match) > 0 {
            if len(match[0]) != len(route) {
                continue
            }
            args := make([]reflect.Value, len(match)-1)

            expectedIn := fv.Type().(*reflect.FuncType).NumIn()
            actualIn := len(match) - 1

            if expectedIn != actualIn {
                message := fmt.Sprintf("%s - Incorrect number of arguments", req.RawURL)
                println(message)
                return
            }

            for i, arg := range match[1:] {
                args[i] = reflect.NewValue(arg)
            }
            ret := fv.Call(args)[0].(*reflect.StringValue).Get()
            var buf bytes.Buffer
            buf.WriteString(ret)
            c.Write(buf.Bytes())
            return
        }
    }

}

func render(tmplString string, context interface{}) (string, os.Error) {

    var tmpl *template.Template
    var err os.Error

    if tmpl, err = template.Parse(tmplString, nil); err != nil {
        return "", err
    }

    var buf bytes.Buffer

    tmpl.Execute(context, &buf)
    return buf.String(), nil
}

func RenderFile(filename string, context interface{}) (string, os.Error) {
    var templateBytes []uint8
    var err os.Error

    if templateBytes, err = ioutil.ReadFile(filename); err != nil {
        return "", err
    }

    return render(string(templateBytes), context)
}

func RenderString(tmplString string, context interface{}) (string, os.Error) {
    return render(tmplString, context)
}

func Run(urls map[string]interface{}, addr string) {
    compileRoutes(urls)
    http.Handle("/", http.HandlerFunc(routeHandler))

    err := http.ListenAndServe(addr, nil)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}
<commit_msg>Added support for request parameter, fixed a couple bugs in web.go<commit_after>package web

import (
    "bytes"
    "fmt"
    "http"
    "io/ioutil"
    "log"
    "os"
    "reflect"
    "regexp"
    "strings"
    "template"
)

type Request http.Request

var compiledRoutes = map[*regexp.Regexp]*reflect.FuncValue{}

func compileRoutes(urls map[string]interface{}) {
    for r, f := range urls {
        regex, err := regexp.Compile(r)
        if err != nil {
            println("Error in route")
        }
        fv := reflect.NewValue(f).(*reflect.FuncValue)
        compiledRoutes[regex] = fv
    }
}

func routeHandler(c *http.Conn, req *http.Request) {
    println(req.RawURL)
    //try to serve a static file
    if strings.HasPrefix(req.RawURL, "/static/") {
        staticFile := req.RawURL[8:]
        if len(staticFile) > 0 {
            http.ServeFile(c, req, "static/"+staticFile)
        }
    }

    var route string = req.URL.Path
    for r, fv := range compiledRoutes {
        if !r.MatchString(route) {
            continue
        }
        match := r.MatchStrings(route)
        if len(match) > 0 {
            if len(match[0]) != len(route) {
                continue
            }
            ai := 0
            routeHandler := fv.Type().(*reflect.FuncType)
            expectedIn := routeHandler.NumIn()
            args := make([]reflect.Value, expectedIn)

            if expectedIn > 0 {
                a0 := routeHandler.In(0)
                ptyp, ok := a0.(*reflect.PtrType)
                if ok {
                    typ := ptyp.Elem()
                    if typ.PkgPath() == "web" && typ.Name() == "Request" {
                        req.ParseForm()
                        wr := (*Request)(req)
                        args[ai] = reflect.NewValue(wr)
                        ai += 1
                        expectedIn -= 1
                    }
                }
            }

            actualIn := len(match) - 1
            if expectedIn != actualIn {
                message := fmt.Sprintf("%s - Incorrect number of arguments", req.RawURL)
                println(message)
                return
            }

            for _, arg := range match[1:] {
                args[ai] = reflect.NewValue(arg)
            }
            ret := fv.Call(args)[0].(*reflect.StringValue).Get()
            var buf bytes.Buffer
            buf.WriteString(ret)
            c.Write(buf.Bytes())
            return
        }
    }
}

func render(tmplString string, context interface{}) (string, os.Error) {

    var tmpl *template.Template
    var err os.Error

    if tmpl, err = template.Parse(tmplString, nil); err != nil {
        return "", err
    }

    var buf bytes.Buffer

    tmpl.Execute(context, &buf)
    return buf.String(), nil
}

func RenderFile(filename string, context interface{}) (string, os.Error) {
    var templateBytes []uint8
    var err os.Error

    if templateBytes, err = ioutil.ReadFile(filename); err != nil {
        return "", err
    }

    return render(string(templateBytes), context)
}

func RenderString(tmplString string, context interface{}) (string, os.Error) {
    return render(tmplString, context)
}

func Run(urls map[string]interface{}, addr string) {
    compileRoutes(urls)
    http.Handle("/", http.HandlerFunc(routeHandler))

    err := http.ListenAndServe(addr, nil)
    if err != nil {
        log.Exit("ListenAndServe:", err)
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Jonathan Chappelow
// See LICENSE for details.

package main

import (
	"bytes"
	"html/template"
	"io"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"sync"
	"time"

	"github.com/dcrdata/dcrdata/blockdata"
	apitypes "github.com/dcrdata/dcrdata/dcrdataapi"
	"github.com/dcrdata/dcrdata/mempool"
	"github.com/decred/dcrd/chaincfg"
)

func TemplateExecToString(t *template.Template, name string, data interface{}) (string, error) {
	var page bytes.Buffer
	err := t.ExecuteTemplate(&page, name, data)
	return page.String(), err
}

type WebTemplateData struct {
	BlockSummary   apitypes.BlockDataBasic
	StakeSummary   apitypes.StakeInfoExtendedEstimates
	MempoolFeeInfo apitypes.MempoolTicketFeeInfo
	MempoolFees    apitypes.MempoolTicketFees
}

type WebUI struct {
	MPC          mempool.MempoolDataCache
	TemplateData WebTemplateData
	templateDataMtx sync.RWMutex
	templ        *template.Template
	templFiles   []string
	params       *chaincfg.Params
}

func NewWebUI() *WebUI {
	fp := filepath.Join("views", "root.tmpl")
	tmpl, err := template.New("home").ParseFiles(fp)
	if err != nil {
		return nil
	}

	//var templFiles []string
	templFiles := []string{fp}

	return &WebUI{
		templ:      tmpl,
		templFiles: templFiles,
		params:     activeChain,
	}
}

func (td *WebUI) ParseTemplates() (err error) {
	td.templ, err = template.New("home").ParseFiles(td.templFiles...)
	return
}

// See reloadsig*.go for an exported method
func (td *WebUI) reloadTemplatesSig(sig os.Signal) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, sig)

	go func() {
		for {
			sigr := <-sigChan
			log.Infof("Received %s", sig)
			if sigr == sig {
				if err := td.ParseTemplates(); err != nil {
					log.Error(err)
					continue
				}
				log.Infof("Web UI html templates reparsed.")
			}
		}
	}()
}

func (td *WebUI) Store(blockData *blockdata.BlockData) error {
	td.templateDataMtx.Lock()
	defer td.templateDataMtx.Unlock()
	td.TemplateData.BlockSummary = blockData.ToBlockSummary()
	td.TemplateData.StakeSummary = blockData.ToStakeInfoExtendedEstimates()
	return nil
}

func (td *WebUI) StoreMPData(data *mempool.MempoolData, timestamp time.Time) error {
	td.MPC.StoreMPData(data, timestamp)

	td.MPC.RLock()
	defer td.MPC.RUnlock()

	_, fie := td.MPC.GetFeeInfoExtra()

	td.templateDataMtx.Lock()
	defer td.templateDataMtx.Unlock()
	td.TemplateData.MempoolFeeInfo = *fie

	// LowestMineable is the lowest fee of those in the top 20 (mainnet), but
	// for the web interface, we want to interpret "lowest mineable" as the
	// lowest fee the user needs to get a new ticket purchase mined right away.
	if td.TemplateData.MempoolFeeInfo.Number < uint32(td.params.MaxFreshStakePerBlock) {
		td.TemplateData.MempoolFeeInfo.LowestMineable = 0.01
	}

	mpf := &td.TemplateData.MempoolFees
	mpf.Height, mpf.Time, _, mpf.FeeRates = td.MPC.GetFeeRates(25)
	mpf.Length = uint32(len(mpf.FeeRates))

	return nil
}

func (td *WebUI) RootPage(w http.ResponseWriter, r *http.Request) {
	td.templateDataMtx.RLock()
	//err := td.templ.Execute(w, td.TemplateData)
	str, err := TemplateExecToString(td.templ, "home", td.TemplateData)
	td.templateDataMtx.RUnlock()
	if err != nil {
		http.Error(w, "template execute failure", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html")
	w.WriteHeader(http.StatusOK)
	io.WriteString(w, str)
}
<commit_msg>fmt web.go<commit_after>// Copyright (c) 2017, Jonathan Chappelow
// See LICENSE for details.

package main

import (
	"bytes"
	"html/template"
	"io"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"sync"
	"time"

	"github.com/dcrdata/dcrdata/blockdata"
	apitypes "github.com/dcrdata/dcrdata/dcrdataapi"
	"github.com/dcrdata/dcrdata/mempool"
	"github.com/decred/dcrd/chaincfg"
)

func TemplateExecToString(t *template.Template, name string, data interface{}) (string, error) {
	var page bytes.Buffer
	err := t.ExecuteTemplate(&page, name, data)
	return page.String(), err
}

type WebTemplateData struct {
	BlockSummary   apitypes.BlockDataBasic
	StakeSummary   apitypes.StakeInfoExtendedEstimates
	MempoolFeeInfo apitypes.MempoolTicketFeeInfo
	MempoolFees    apitypes.MempoolTicketFees
}

type WebUI struct {
	MPC             mempool.MempoolDataCache
	TemplateData    WebTemplateData
	templateDataMtx sync.RWMutex
	templ           *template.Template
	templFiles      []string
	params          *chaincfg.Params
}

func NewWebUI() *WebUI {
	fp := filepath.Join("views", "root.tmpl")
	tmpl, err := template.New("home").ParseFiles(fp)
	if err != nil {
		return nil
	}

	//var templFiles []string
	templFiles := []string{fp}

	return &WebUI{
		templ:      tmpl,
		templFiles: templFiles,
		params:     activeChain,
	}
}

func (td *WebUI) ParseTemplates() (err error) {
	td.templ, err = template.New("home").ParseFiles(td.templFiles...)
	return
}

// See reloadsig*.go for an exported method
func (td *WebUI) reloadTemplatesSig(sig os.Signal) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, sig)

	go func() {
		for {
			sigr := <-sigChan
			log.Infof("Received %s", sig)
			if sigr == sig {
				if err := td.ParseTemplates(); err != nil {
					log.Error(err)
					continue
				}
				log.Infof("Web UI html templates reparsed.")
			}
		}
	}()
}

func (td *WebUI) Store(blockData *blockdata.BlockData) error {
	td.templateDataMtx.Lock()
	defer td.templateDataMtx.Unlock()
	td.TemplateData.BlockSummary = blockData.ToBlockSummary()
	td.TemplateData.StakeSummary = blockData.ToStakeInfoExtendedEstimates()
	return nil
}

func (td *WebUI) StoreMPData(data *mempool.MempoolData, timestamp time.Time) error {
	td.MPC.StoreMPData(data, timestamp)

	td.MPC.RLock()
	defer td.MPC.RUnlock()

	_, fie := td.MPC.GetFeeInfoExtra()

	td.templateDataMtx.Lock()
	defer td.templateDataMtx.Unlock()
	td.TemplateData.MempoolFeeInfo = *fie

	// LowestMineable is the lowest fee of those in the top 20 (mainnet), but
	// for the web interface, we want to interpret "lowest mineable" as the
	// lowest fee the user needs to get a new ticket purchase mined right away.
	if td.TemplateData.MempoolFeeInfo.Number < uint32(td.params.MaxFreshStakePerBlock) {
		td.TemplateData.MempoolFeeInfo.LowestMineable = 0.01
	}

	mpf := &td.TemplateData.MempoolFees
	mpf.Height, mpf.Time, _, mpf.FeeRates = td.MPC.GetFeeRates(25)
	mpf.Length = uint32(len(mpf.FeeRates))

	return nil
}

func (td *WebUI) RootPage(w http.ResponseWriter, r *http.Request) {
	td.templateDataMtx.RLock()
	//err := td.templ.Execute(w, td.TemplateData)
	str, err := TemplateExecToString(td.templ, "home", td.TemplateData)
	td.templateDataMtx.RUnlock()
	if err != nil {
		http.Error(w, "template execute failure", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html")
	w.WriteHeader(http.StatusOK)
	io.WriteString(w, str)
}
<|endoftext|>


<commit_before>package main

import (
  "os"
  "log"
  "net"
  "strings"
)

import _ "github.com/joho/godotenv/autoload"

func writeWithErrorLog(c net.Conn, data string) {
  _, err := c.Write([]byte(data))
  if (err != nil) {
    log.Fatal(err)
  }
}

func writeWithTab(c net.Conn, data string) {
  writeWithErrorLog(c, data + "\t")
}

func response(c net.Conn, code string, title string, path string, server string, port string) {
  writeWithErrorLog(c, code)
  writeWithTab(c, title)
  writeWithTab(c, path)
  writeWithTab(c, server)
  writeWithTab(c, port)
  writeWithErrorLog(c, "\r\n")
}

func consumeRequest(c net.Conn, path string, arguments []string) {
  writeWithErrorLog(c, "/\r\n")
  response(c, "i", "Tacos are great!", "null", "(FALSE)", "0")
  response(c, "i", "", "null", "(FALSE)", "0")
  response(c, "i", "", "null", "(FALSE)", "0")
  response(c, "i", "I really like them.", "null", "(FALSE)", "0")
  response(c, "i", "You are at: " + path, "null", "(FALSE)", "0")
  writeWithErrorLog(c, ".\r\n")
}

func extractRequest(c net.Conn) (string, []string, error)  {
  buf := make([]byte, 4096)

  n, err := c.Read(buf)
  log.Printf("Read %v bytes", n)
  if err != nil {
    log.Fatal("Read error in %v", buf)
    return "", nil, err
  }

  if n == 0 {
    log.Printf("Got null request")
    return "", nil, nil
  }

  parts := strings.Split(string(buf), "\t")
  return parts[0], parts[1:], nil
}

func handleConnection(c net.Conn) {
  path, arguments, err := extractRequest(c)

  if (err != nil) {
    log.Fatal("Request error: %v", err)
  } else {
    consumeRequest(c, path, arguments)
  }

  c.Close()
  log.Printf("Connection from %v closed.", c.RemoteAddr())
}

func listenOrDie(port string) (net.Listener) {
  ln, err := net.Listen("tcp", ":" + port)

  if err != nil {
    panic(err)
  }

  return ln
}

func acceptOrWhatever(ln net.Listener) (net.Conn) {
  conn, err := ln.Accept()

  if err != nil {
    log.Printf("Hit accept error %v", err)
  } else {
    log.Printf("Connection from %v opened.", conn.RemoteAddr())
  }

  return conn
}

func main() {
  port := os.Getenv("PORT")
  ln := listenOrDie(port)

  log.Printf("Listening on gopher://localhost:%v", port)

  for {
    conn := acceptOrWhatever(ln)
    if conn != nil {
      go handleConnection(conn)
    }
  }
}
<commit_msg>Alert upfront about read errors<commit_after>package main

import (
  "os"
  "log"
  "net"
  "strings"
)

import _ "github.com/joho/godotenv/autoload"

func writeWithErrorLog(c net.Conn, data string) {
  _, err := c.Write([]byte(data))
  if (err != nil) {
    log.Fatal(err)
  }
}

func writeWithTab(c net.Conn, data string) {
  writeWithErrorLog(c, data + "\t")
}

func response(c net.Conn, code string, title string, path string, server string, port string) {
  writeWithErrorLog(c, code)
  writeWithTab(c, title)
  writeWithTab(c, path)
  writeWithTab(c, server)
  writeWithTab(c, port)
  writeWithErrorLog(c, "\r\n")
}

func consumeRequest(c net.Conn, path string, arguments []string) {
  writeWithErrorLog(c, "/\r\n")
  response(c, "i", "Tacos are great!", "null", "(FALSE)", "0")
  response(c, "i", "", "null", "(FALSE)", "0")
  response(c, "i", "", "null", "(FALSE)", "0")
  response(c, "i", "I really like them.", "null", "(FALSE)", "0")
  response(c, "i", "You are at: " + path, "null", "(FALSE)", "0")
  writeWithErrorLog(c, ".\r\n")
}

func extractRequest(c net.Conn) (string, []string, error)  {
  buf := make([]byte, 4096)

  n, err := c.Read(buf)
  log.Printf("Read %v bytes", n)
  if err != nil {
    log.Fatal("Read error: %v", err)
    return "", nil, err
  }

  if n == 0 {
    log.Printf("Got null request")
    return "", nil, nil
  }

  parts := strings.Split(string(buf), "\t")
  return parts[0], parts[1:], nil
}

func handleConnection(c net.Conn) {
  path, arguments, err := extractRequest(c)

  if (err != nil) {
    log.Fatal("Request error: %v", err)
  } else {
    consumeRequest(c, path, arguments)
  }

  c.Close()
  log.Printf("Connection from %v closed.", c.RemoteAddr())
}

func listenOrDie(port string) (net.Listener) {
  ln, err := net.Listen("tcp", ":" + port)

  if err != nil {
    panic(err)
  }

  return ln
}

func acceptOrWhatever(ln net.Listener) (net.Conn) {
  conn, err := ln.Accept()

  if err != nil {
    log.Printf("Hit accept error %v", err)
  } else {
    log.Printf("Connection from %v opened.", conn.RemoteAddr())
  }

  return conn
}

func main() {
  port := os.Getenv("PORT")
  ln := listenOrDie(port)

  log.Printf("Listening on gopher://localhost:%v", port)

  for {
    conn := acceptOrWhatever(ln)
    if conn != nil {
      go handleConnection(conn)
    }
  }
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 kubernetes

import (
	"fmt"
	"time"

	apiv1 "k8s.io/kubernetes/pkg/api/v1"
)

// IsNodeReadyAndSchedulable returns true if the node is ready and schedulable.
func IsNodeReadyAndSchedulable(node *apiv1.Node) bool {
	ready, _, _ := GetReadinessState(node)
	if !ready {
		return false
	}
	// Ignore nodes that are marked unschedulable
	if node.Spec.Unschedulable {
		return false
	}
	return true
}

// GetReadinessState gets readiness state for the node
func GetReadinessState(node *apiv1.Node) (isNodeReady bool, lastTransitionTime time.Time, err error) {
	canNodeBeReady, readyFound := true, false
	lastTransitionTime = time.Time{}

	for _, cond := range node.Status.Conditions {
		switch cond.Type {
		case apiv1.NodeReady:
			readyFound = true
			if cond.Status == apiv1.ConditionFalse {
				canNodeBeReady = false
			}
			if lastTransitionTime.Before(cond.LastTransitionTime.Time) {
				lastTransitionTime = cond.LastTransitionTime.Time
			}
		case apiv1.NodeOutOfDisk:
			if cond.Status == apiv1.ConditionTrue {
				canNodeBeReady = false
			}
			if lastTransitionTime.Before(cond.LastTransitionTime.Time) {
				lastTransitionTime = cond.LastTransitionTime.Time
			}
		case apiv1.NodeNetworkUnavailable:
			if cond.Status == apiv1.ConditionTrue {
				canNodeBeReady = false
			}
			if lastTransitionTime.Before(cond.LastTransitionTime.Time) {
				lastTransitionTime = cond.LastTransitionTime.Time
			}
		}
	}
	if !readyFound {
		return false, time.Time{}, fmt.Errorf("feadiness information not found")
	}
	return canNodeBeReady, lastTransitionTime, nil
}
<commit_msg>Cluster-Autoscaler: "unknown" readiness -> unready<commit_after>/*
Copyright 2016 The Kubernetes 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 kubernetes

import (
	"fmt"
	"time"

	apiv1 "k8s.io/kubernetes/pkg/api/v1"
)

// IsNodeReadyAndSchedulable returns true if the node is ready and schedulable.
func IsNodeReadyAndSchedulable(node *apiv1.Node) bool {
	ready, _, _ := GetReadinessState(node)
	if !ready {
		return false
	}
	// Ignore nodes that are marked unschedulable
	if node.Spec.Unschedulable {
		return false
	}
	return true
}

// GetReadinessState gets readiness state for the node
func GetReadinessState(node *apiv1.Node) (isNodeReady bool, lastTransitionTime time.Time, err error) {
	canNodeBeReady, readyFound := true, false
	lastTransitionTime = time.Time{}

	for _, cond := range node.Status.Conditions {
		switch cond.Type {
		case apiv1.NodeReady:
			readyFound = true
			if cond.Status == apiv1.ConditionFalse || cond.Status == apiv1.ConditionUnknown {
				canNodeBeReady = false
			}
			if lastTransitionTime.Before(cond.LastTransitionTime.Time) {
				lastTransitionTime = cond.LastTransitionTime.Time
			}
		case apiv1.NodeOutOfDisk:
			if cond.Status == apiv1.ConditionTrue {
				canNodeBeReady = false
			}
			if lastTransitionTime.Before(cond.LastTransitionTime.Time) {
				lastTransitionTime = cond.LastTransitionTime.Time
			}
		case apiv1.NodeNetworkUnavailable:
			if cond.Status == apiv1.ConditionTrue {
				canNodeBeReady = false
			}
			if lastTransitionTime.Before(cond.LastTransitionTime.Time) {
				lastTransitionTime = cond.LastTransitionTime.Time
			}
		}
	}
	if !readyFound {
		return false, time.Time{}, fmt.Errorf("feadiness information not found")
	}
	return canNodeBeReady, lastTransitionTime, nil
}
<|endoftext|>


<commit_before>package collectors

import (
	"fmt"
	"io/ioutil"

	"strconv"
	"strings"

	"github.com/bosun-monitor/scollector/metadata"
	"github.com/bosun-monitor/scollector/opentsdb"
)

func WatchProcesses(procs []*WatchedProc) error {
	collectors = append(collectors, &IntervalCollector{
		F: func() (opentsdb.MultiDataPoint, error) {
			return c_linux_processes(procs)
		},
	})
	return nil
}

func linuxProcMonitor(w *WatchedProc, md *opentsdb.MultiDataPoint) error {
	var err error
	for pid, id := range w.Processes {
		id := strconv.Itoa(id)
		stats_file, err := ioutil.ReadFile("/proc/" + pid + "/stat")
		if err != nil {
			w.Remove(pid)
			return
		}
		io_file, err := ioutil.ReadFile("/proc/" + pid + "/io")
		if err != nil {
			w.Remove(pid)
			return
		}
		stats := strings.Fields(string(stats_file))
		if len(stats) < 24 {
			err = fmt.Errorf("stats too short")
			continue
		}
		var io []string
		for _, line := range strings.Split(string(io_file), "\n") {
			f := strings.Fields(line)
			if len(f) == 2 {
				io = append(io, f[1])
			}
		}
		if len(io) < 6 {
			err = fmt.Errorf("io too short")
			continue
		}
		tags := opentsdb.TagSet{"name": w.Name, "id": id}
		Add(md, "linux.proc.cpu", stats[13], opentsdb.TagSet{"type": "user"}.Merge(tags), metadata.Counter, metadata.Pct, descLinuxProcCpuUser)
		Add(md, "linux.proc.cpu", stats[14], opentsdb.TagSet{"type": "system"}.Merge(tags), metadata.Counter, metadata.Pct, descLinuxProcCpuSystem)
		Add(md, "linux.proc.mem.fault", stats[9], opentsdb.TagSet{"type": "minflt"}.Merge(tags), metadata.Counter, metadata.Fault, descLinuxProcMemFaultMin)
		Add(md, "linux.proc.mem.fault", stats[11], opentsdb.TagSet{"type": "majflt"}.Merge(tags), metadata.Counter, metadata.Fault, descLinuxProcMemFaultMax)
		Add(md, "linux.proc.mem.virtual", stats[22], tags, metadata.Gauge, metadata.Bytes, descLinuxProcMemVirtual)
		Add(md, "linux.proc.mem.rss", stats[23], tags, metadata.Gauge, metadata.Page, descLinuxProcMemRss)
		Add(md, "linux.proc.char_io", io[0], opentsdb.TagSet{"type": "read"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcCharIoRead)
		Add(md, "linux.proc.char_io", io[1], opentsdb.TagSet{"type": "write"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcCharIoWrite)
		Add(md, "linux.proc.syscall", io[2], opentsdb.TagSet{"type": "read"}.Merge(tags), metadata.Counter, metadata.Syscall, descLinuxProcSyscallRead)
		Add(md, "linux.proc.syscall", io[3], opentsdb.TagSet{"type": "write"}.Merge(tags), metadata.Counter, metadata.Syscall, descLinuxProcSyscallWrite)
		Add(md, "linux.proc.io_bytes", io[4], opentsdb.TagSet{"type": "read"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcIoBytesRead)
		Add(md, "linux.proc.io_bytes", io[5], opentsdb.TagSet{"type": "write"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcIoBytesWrite)
	}
	return error
}

const (
	descLinuxProcCpuUser      = "The amount of time that this process has been scheduled in user mode."
	descLinuxProcCpuSystem    = "The amount of time that this process has been scheduled in kernel mode"
	descLinuxProcMemFaultMin  = "The number of minor faults the process has made which have not required loading a memory page from disk."
	descLinuxProcMemFaultMax  = "The number of major faults the process has made which have required loading a memory page from disk."
	descLinuxProcMemVirtual   = "The virtual memory size."
	descLinuxProcMemRss       = "The resident set size (number of pages the process has in real memory."
	descLinuxProcCharIoRead   = "The number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which this process passed to read(2) and similar system calls. It includes things such as terminal I/O and is unaffected by whether or not actual physical disk I/O was required (the read might have been satisfied from pagecache)"
	descLinuxProcCharIoWrite  = "The number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats apply here as with read."
	descLinuxProcSyscallRead  = "An attempt to count the number of read I/O operations—that is, system calls such as read(2) and pread(2)."
	descLinuxProcSyscallWrite = "Attempt to count the number of write I/O operations—that is, system calls such as write(2) and pwrite(2)."
	descLinuxProcIoBytesRead  = "An attempt to count the number of bytes which this process really did cause to be fetched from the storage layer. This is accurate for block-backed filesystems."
	descLinuxProcIoBytesWrite = "An Attempt to count the number of bytes which this process caused to be sent to the storage layer."
)

func getLinuxProccesses() ([]*Process, error) {
	files, err := ioutil.ReadDir("/proc")
	if err != nil {
		return nil, err
	}
	var pids []string
	for _, f := range files {
		if _, err := strconv.Atoi(f.Name()); err == nil && f.IsDir() {
			pids = append(pids, f.Name())
		}
	}
	var lps []*Process
	for _, pid := range pids {
		cmdline, err := ioutil.ReadFile("/proc/" + pid + "/cmdline")
		if err != nil {
			//Continue because the pid might not exist any more
			continue
		}
		cl := strings.Split(string(cmdline), "\x00")
		if len(cl) < 1 || len(cl[0]) == 0 {
			continue
		}
		lp := &Process{
			Pid:     pid,
			Command: cl[0],
		}
		if len(cl) > 1 {
			lp.Arguments = strings.Join(cl[1:], "")
		}
		lps = append(lps, lp)
	}
	return lps, nil
}

func c_linux_processes(procs []*WatchedProc) (opentsdb.MultiDataPoint, error) {
	var md opentsdb.MultiDataPoint
	lps, err := getLinuxProccesses()
	if err != nil {
		return nil, nil
	}
	for _, w := range procs {
		w.Check(lp)
		if e := linuxProcMonitor(w, &md); e != nil {
			err = e
		}
	}
	return md, err
}
<commit_msg>cmd/scollector: Simpler<commit_after>package collectors

import (
	"fmt"
	"io/ioutil"

	"strconv"
	"strings"

	"github.com/bosun-monitor/scollector/metadata"
	"github.com/bosun-monitor/scollector/opentsdb"
)

func WatchProcesses(procs []*WatchedProc) error {
	collectors = append(collectors, &IntervalCollector{
		F: func() (opentsdb.MultiDataPoint, error) {
			return c_linux_processes(procs)
		},
	})
	return nil
}

func linuxProcMonitor(w *WatchedProc, md *opentsdb.MultiDataPoint) error {
	var err error
	for pid, id := range w.Processes {
		stats_file, err := ioutil.ReadFile("/proc/" + pid + "/stat")
		if err != nil {
			w.Remove(pid)
			return
		}
		io_file, err := ioutil.ReadFile("/proc/" + pid + "/io")
		if err != nil {
			w.Remove(pid)
			return
		}
		stats := strings.Fields(string(stats_file))
		if len(stats) < 24 {
			err = fmt.Errorf("stats too short")
			continue
		}
		var io []string
		for _, line := range strings.Split(string(io_file), "\n") {
			f := strings.Fields(line)
			if len(f) == 2 {
				io = append(io, f[1])
			}
		}
		if len(io) < 6 {
			err = fmt.Errorf("io too short")
			continue
		}
		tags := opentsdb.TagSet{"name": w.Name, "id": strconv.Itoa(id)}
		Add(md, "linux.proc.cpu", stats[13], opentsdb.TagSet{"type": "user"}.Merge(tags), metadata.Counter, metadata.Pct, descLinuxProcCpuUser)
		Add(md, "linux.proc.cpu", stats[14], opentsdb.TagSet{"type": "system"}.Merge(tags), metadata.Counter, metadata.Pct, descLinuxProcCpuSystem)
		Add(md, "linux.proc.mem.fault", stats[9], opentsdb.TagSet{"type": "minflt"}.Merge(tags), metadata.Counter, metadata.Fault, descLinuxProcMemFaultMin)
		Add(md, "linux.proc.mem.fault", stats[11], opentsdb.TagSet{"type": "majflt"}.Merge(tags), metadata.Counter, metadata.Fault, descLinuxProcMemFaultMax)
		Add(md, "linux.proc.mem.virtual", stats[22], tags, metadata.Gauge, metadata.Bytes, descLinuxProcMemVirtual)
		Add(md, "linux.proc.mem.rss", stats[23], tags, metadata.Gauge, metadata.Page, descLinuxProcMemRss)
		Add(md, "linux.proc.char_io", io[0], opentsdb.TagSet{"type": "read"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcCharIoRead)
		Add(md, "linux.proc.char_io", io[1], opentsdb.TagSet{"type": "write"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcCharIoWrite)
		Add(md, "linux.proc.syscall", io[2], opentsdb.TagSet{"type": "read"}.Merge(tags), metadata.Counter, metadata.Syscall, descLinuxProcSyscallRead)
		Add(md, "linux.proc.syscall", io[3], opentsdb.TagSet{"type": "write"}.Merge(tags), metadata.Counter, metadata.Syscall, descLinuxProcSyscallWrite)
		Add(md, "linux.proc.io_bytes", io[4], opentsdb.TagSet{"type": "read"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcIoBytesRead)
		Add(md, "linux.proc.io_bytes", io[5], opentsdb.TagSet{"type": "write"}.Merge(tags), metadata.Counter, metadata.Bytes, descLinuxProcIoBytesWrite)
	}
	return error
}

const (
	descLinuxProcCpuUser      = "The amount of time that this process has been scheduled in user mode."
	descLinuxProcCpuSystem    = "The amount of time that this process has been scheduled in kernel mode"
	descLinuxProcMemFaultMin  = "The number of minor faults the process has made which have not required loading a memory page from disk."
	descLinuxProcMemFaultMax  = "The number of major faults the process has made which have required loading a memory page from disk."
	descLinuxProcMemVirtual   = "The virtual memory size."
	descLinuxProcMemRss       = "The resident set size (number of pages the process has in real memory."
	descLinuxProcCharIoRead   = "The number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which this process passed to read(2) and similar system calls. It includes things such as terminal I/O and is unaffected by whether or not actual physical disk I/O was required (the read might have been satisfied from pagecache)"
	descLinuxProcCharIoWrite  = "The number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats apply here as with read."
	descLinuxProcSyscallRead  = "An attempt to count the number of read I/O operations—that is, system calls such as read(2) and pread(2)."
	descLinuxProcSyscallWrite = "Attempt to count the number of write I/O operations—that is, system calls such as write(2) and pwrite(2)."
	descLinuxProcIoBytesRead  = "An attempt to count the number of bytes which this process really did cause to be fetched from the storage layer. This is accurate for block-backed filesystems."
	descLinuxProcIoBytesWrite = "An Attempt to count the number of bytes which this process caused to be sent to the storage layer."
)

func getLinuxProccesses() ([]*Process, error) {
	files, err := ioutil.ReadDir("/proc")
	if err != nil {
		return nil, err
	}
	var pids []string
	for _, f := range files {
		if _, err := strconv.Atoi(f.Name()); err == nil && f.IsDir() {
			pids = append(pids, f.Name())
		}
	}
	var lps []*Process
	for _, pid := range pids {
		cmdline, err := ioutil.ReadFile("/proc/" + pid + "/cmdline")
		if err != nil {
			//Continue because the pid might not exist any more
			continue
		}
		cl := strings.Split(string(cmdline), "\x00")
		if len(cl) < 1 || len(cl[0]) == 0 {
			continue
		}
		lp := &Process{
			Pid:     pid,
			Command: cl[0],
		}
		if len(cl) > 1 {
			lp.Arguments = strings.Join(cl[1:], "")
		}
		lps = append(lps, lp)
	}
	return lps, nil
}

func c_linux_processes(procs []*WatchedProc) (opentsdb.MultiDataPoint, error) {
	var md opentsdb.MultiDataPoint
	lps, err := getLinuxProccesses()
	if err != nil {
		return nil, nil
	}
	for _, w := range procs {
		w.Check(lp)
		if e := linuxProcMonitor(w, &md); e != nil {
			err = e
		}
	}
	return md, err
}
<|endoftext|>


<commit_before>package common

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"strings"
	"sync"
	"time"
)

var (
	GoPath = os.Getenv("GOPATH")
)

func TrapSignal(cb func()) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, os.Kill)
	go func() {
		for sig := range c {
			fmt.Printf("captured %v, exiting...\n", sig)
			if cb != nil {
				cb()
			}
			os.Exit(1)
		}
	}()
	select {}
}

func Exit(s string) {
	fmt.Printf(s + "\n")
	os.Exit(1)
}

func EnsureDir(dir string) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		err := os.MkdirAll(dir, 0700)
		if err != nil {
			return fmt.Errorf("Could not create directory %v. %v", dir, err)
		}
	}
	return nil
}

func FileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return !os.IsNotExist(err)
}

func ReadFile(filePath string) ([]byte, error) {
	return ioutil.ReadFile(filePath)
}

func MustReadFile(filePath string) []byte {
	fileBytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		Exit(Fmt("MustReadFile failed: %v", err))
		return nil
	}
	return fileBytes
}

func WriteFile(filePath string, contents []byte) error {
	err := ioutil.WriteFile(filePath, contents, 0600)
	if err != nil {
		return err
	}
	// fmt.Printf("File written to %v.\n", filePath)
	return nil
}

func MustWriteFile(filePath string, contents []byte) {
	err := WriteFile(filePath, contents)
	if err != nil {
		Exit(Fmt("MustWriteFile failed: %v", err))
	}
}

// Writes to newBytes to filePath.
// Guaranteed not to lose *both* oldBytes and newBytes,
// (assuming that the OS is perfect)
func WriteFileAtomic(filePath string, newBytes []byte) error {
	// If a file already exists there, copy to filePath+".bak" (overwrite anything)
	if _, err := os.Stat(filePath); !os.IsNotExist(err) {
		fileBytes, err := ioutil.ReadFile(filePath)
		if err != nil {
			return fmt.Errorf("Could not read file %v. %v", filePath, err)
		}
		err = ioutil.WriteFile(filePath+".bak", fileBytes, 0600)
		if err != nil {
			return fmt.Errorf("Could not write file %v. %v", filePath+".bak", err)
		}
	}
	// Write newBytes to filePath.new
	err := ioutil.WriteFile(filePath+".new", newBytes, 0600)
	if err != nil {
		return fmt.Errorf("Could not write file %v. %v", filePath+".new", err)
	}
	// Move filePath.new to filePath
	err = os.Rename(filePath+".new", filePath)
	return err
}

//--------------------------------------------------------------------------------

/* AutoFile usage

// Create/Append to ./autofile_test
af, err := OpenAutoFile("autofile_test")
if err != nil {
	panic(err)
}

// Stream of writes.
// During this time, the file may be moved e.g. by logRotate.
for i := 0; i < 60; i++ {
	af.Write([]byte(Fmt("LOOP(%v)", i)))
	time.Sleep(time.Second)
}

// Close the AutoFile
err = af.Close()
if err != nil {
	panic(err)
}
*/

const autoFileOpenDuration = 1000 * time.Millisecond

// Automatically closes and re-opens file for writing.
// This is useful for using a log file with the logrotate tool.
type AutoFile struct {
	Path   string
	ticker *time.Ticker
	mtx    sync.Mutex
	file   *os.File
}

func OpenAutoFile(path string) (af *AutoFile, err error) {
	af = &AutoFile{
		Path:   path,
		ticker: time.NewTicker(autoFileOpenDuration),
	}
	if err = af.openFile(); err != nil {
		return
	}
	go af.processTicks()
	return
}

func (af *AutoFile) Close() error {
	af.ticker.Stop()
	af.mtx.Lock()
	err := af.closeFile()
	af.mtx.Unlock()
	return err
}

func (af *AutoFile) processTicks() {
	for {
		_, ok := <-af.ticker.C
		if !ok {
			return // Done.
		}
		af.mtx.Lock()
		af.closeFile()
		af.mtx.Unlock()
	}
}

func (af *AutoFile) closeFile() (err error) {
	file := af.file
	if file == nil {
		return nil
	}
	af.file = nil
	return file.Close()
}

func (af *AutoFile) Write(b []byte) (n int, err error) {
	af.mtx.Lock()
	defer af.mtx.Unlock()
	if af.file == nil {
		if err = af.openFile(); err != nil {
			return
		}
	}
	return af.file.Write(b)
}

func (af *AutoFile) openFile() error {
	file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	if err != nil {
		return err
	}
	af.file = file
	return nil
}

func Tempfile(prefix string) (*os.File, string) {
	file, err := ioutil.TempFile("", prefix)
	if err != nil {
		PanicCrisis(err)
	}
	return file, file.Name()
}

func Prompt(prompt string, defaultValue string) (string, error) {
	fmt.Print(prompt)
	reader := bufio.NewReader(os.Stdin)
	line, err := reader.ReadString('\n')
	if err != nil {
		return defaultValue, err
	} else {
		line = strings.TrimSpace(line)
		if line == "" {
			return defaultValue, nil
		}
		return line, nil
	}
}
<commit_msg>EnsureDir() takes file mode<commit_after>package common

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"strings"
	"sync"
	"time"
)

var (
	GoPath = os.Getenv("GOPATH")
)

func TrapSignal(cb func()) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, os.Kill)
	go func() {
		for sig := range c {
			fmt.Printf("captured %v, exiting...\n", sig)
			if cb != nil {
				cb()
			}
			os.Exit(1)
		}
	}()
	select {}
}

func Exit(s string) {
	fmt.Printf(s + "\n")
	os.Exit(1)
}

func EnsureDir(dir string, mode os.FileMode) error {
	if _, err := os.Stat(dir); os.IsNotExist(err) {
		err := os.MkdirAll(dir, mode)
		if err != nil {
			return fmt.Errorf("Could not create directory %v. %v", dir, err)
		}
	}
	return nil
}

func FileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return !os.IsNotExist(err)
}

func ReadFile(filePath string) ([]byte, error) {
	return ioutil.ReadFile(filePath)
}

func MustReadFile(filePath string) []byte {
	fileBytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		Exit(Fmt("MustReadFile failed: %v", err))
		return nil
	}
	return fileBytes
}

func WriteFile(filePath string, contents []byte) error {
	err := ioutil.WriteFile(filePath, contents, 0600)
	if err != nil {
		return err
	}
	// fmt.Printf("File written to %v.\n", filePath)
	return nil
}

func MustWriteFile(filePath string, contents []byte) {
	err := WriteFile(filePath, contents)
	if err != nil {
		Exit(Fmt("MustWriteFile failed: %v", err))
	}
}

// Writes to newBytes to filePath.
// Guaranteed not to lose *both* oldBytes and newBytes,
// (assuming that the OS is perfect)
func WriteFileAtomic(filePath string, newBytes []byte) error {
	// If a file already exists there, copy to filePath+".bak" (overwrite anything)
	if _, err := os.Stat(filePath); !os.IsNotExist(err) {
		fileBytes, err := ioutil.ReadFile(filePath)
		if err != nil {
			return fmt.Errorf("Could not read file %v. %v", filePath, err)
		}
		err = ioutil.WriteFile(filePath+".bak", fileBytes, 0600)
		if err != nil {
			return fmt.Errorf("Could not write file %v. %v", filePath+".bak", err)
		}
	}
	// Write newBytes to filePath.new
	err := ioutil.WriteFile(filePath+".new", newBytes, 0600)
	if err != nil {
		return fmt.Errorf("Could not write file %v. %v", filePath+".new", err)
	}
	// Move filePath.new to filePath
	err = os.Rename(filePath+".new", filePath)
	return err
}

//--------------------------------------------------------------------------------

/* AutoFile usage

// Create/Append to ./autofile_test
af, err := OpenAutoFile("autofile_test")
if err != nil {
	panic(err)
}

// Stream of writes.
// During this time, the file may be moved e.g. by logRotate.
for i := 0; i < 60; i++ {
	af.Write([]byte(Fmt("LOOP(%v)", i)))
	time.Sleep(time.Second)
}

// Close the AutoFile
err = af.Close()
if err != nil {
	panic(err)
}
*/

const autoFileOpenDuration = 1000 * time.Millisecond

// Automatically closes and re-opens file for writing.
// This is useful for using a log file with the logrotate tool.
type AutoFile struct {
	Path   string
	ticker *time.Ticker
	mtx    sync.Mutex
	file   *os.File
}

func OpenAutoFile(path string) (af *AutoFile, err error) {
	af = &AutoFile{
		Path:   path,
		ticker: time.NewTicker(autoFileOpenDuration),
	}
	if err = af.openFile(); err != nil {
		return
	}
	go af.processTicks()
	return
}

func (af *AutoFile) Close() error {
	af.ticker.Stop()
	af.mtx.Lock()
	err := af.closeFile()
	af.mtx.Unlock()
	return err
}

func (af *AutoFile) processTicks() {
	for {
		_, ok := <-af.ticker.C
		if !ok {
			return // Done.
		}
		af.mtx.Lock()
		af.closeFile()
		af.mtx.Unlock()
	}
}

func (af *AutoFile) closeFile() (err error) {
	file := af.file
	if file == nil {
		return nil
	}
	af.file = nil
	return file.Close()
}

func (af *AutoFile) Write(b []byte) (n int, err error) {
	af.mtx.Lock()
	defer af.mtx.Unlock()
	if af.file == nil {
		if err = af.openFile(); err != nil {
			return
		}
	}
	return af.file.Write(b)
}

func (af *AutoFile) openFile() error {
	file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	if err != nil {
		return err
	}
	af.file = file
	return nil
}

func Tempfile(prefix string) (*os.File, string) {
	file, err := ioutil.TempFile("", prefix)
	if err != nil {
		PanicCrisis(err)
	}
	return file, file.Name()
}

func Prompt(prompt string, defaultValue string) (string, error) {
	fmt.Print(prompt)
	reader := bufio.NewReader(os.Stdin)
	line, err := reader.ReadString('\n')
	if err != nil {
		return defaultValue, err
	} else {
		line = strings.TrimSpace(line)
		if line == "" {
			return defaultValue, nil
		}
		return line, nil
	}
}
<|endoftext|>