<commit_before>// © 2014 Steve McCoy under the MIT license. See LICENSE for details.

/*
Package ogg implements encoding and decoding of OGG streams as defined in
http://xiph.org/ogg/doc/rfc3533.txt
and
http://xiph.org/ogg/doc/framing.html .
*/
package ogg

import (
	"bytes"
	"encoding/binary"
	"hash/crc32"
	"io"
)

const MIMEType = "application/ogg"

var ByteOrder = binary.LittleEndian

type PageHeader struct {
	oggS          [4]byte // 0-3, always == "OggS"
	streamVersion byte    // 4, always == 0
	headerType    byte    // 5
	Granule       int64   // 6-13, codec-specific
	serial        uint32  // 14-17, associated with a logical stream
	page          uint32  // 18-21, sequence number of page in packet
	crc           uint32  // 22-25
	nsegs         byte    // 26
}

const (
	// Continuation of packet
	cop byte = 1 + iota
	// Beginning of stream
	bos = 1 << iota
	// End of stream
	eos = 1 << iota
)

var crcTable = crc32.MakeTable(0x04c11db7)

type PageReader interface {
	Read() ([]byte, error)
}

type Writer struct {
	serial uint32
	w      io.Writer
	//TODO(mccoyst): add a function for determining granule
}

func NewWriter(id uint32, w io.Writer) *Writer {
	return &Writer{id, w}
}

func (w *Writer) WriteBOS(packet []byte) error {
	_, err := w.writePacket(bos, packet)
	return err
}

func (w *Writer) Write(packet []byte) (int, error) {
	return w.writePacket(0, packet)
}

func (w *Writer) WriteEOS() error {
	_, err := w.writePacket(eos, nil)
	return err
}

func (w *Writer) writePacket(kind byte, packet []byte) (int, error) {
	h := PageHeader{
		oggS:       [4]byte{'O', 'g', 'g', 'S'},
		headerType: kind,
		serial:     w.serial,
	}

	var err error
	n, m := 0, 0
	const mps = 255 * 255 // maximum 255 segments of 255 bytes in a page

	s := 0
	e := s + mps
	if e > len(packet) {
		e = len(packet)
	}
	page := packet[s:e]
	n, err = w.writePage(page, &h)
	if err != nil {
		return n, err
	}
	s = e

	last := (len(packet) / mps) * mps
	h.headerType &= cop
	for s < last {
		h.page++
		e = s + mps
		page = packet[s:e]
		m, err = w.writePage(page, &h)
		n += m
		if err != nil {
			return n, err
		}
		s = e
	}

	if len(packet) > 0 {
		m, err = w.writePage(packet[last:], &h)
		n += m
	}
	return n, err
}

func (w *Writer) writePage(page []byte, h *PageHeader) (int, error) {
	h.nsegs = byte(len(page)/255 + 1)
	segtbl := make([]byte, h.nsegs)
	for i := 0; i < len(segtbl)-1; i++ {
		segtbl[i] = 255
	}
	segtbl[len(segtbl)-1] = byte(len(page) % 255)

	var hb bytes.Buffer
	err := binary.Write(&hb, ByteOrder, &h)
	if err != nil {
		return 0, err
	}

	hb.Write(segtbl)
	hb.Write(page)

	bb := hb.Bytes()
	crc := crc32.Checksum(bb, crcTable)
	err = binary.Write(bytes.NewBuffer(bb[22:26]), ByteOrder, crc)
	if err != nil {
		return 0, nil
	}

	n64, err := hb.WriteTo(w.w)
	return int(n64), err
}
<commit_msg>Change Writer API to Encoder<commit_after>// © 2014 Steve McCoy under the MIT license. See LICENSE for details.

/*
Package ogg implements encoding and decoding of OGG streams as defined in
http://xiph.org/ogg/doc/rfc3533.txt
and
http://xiph.org/ogg/doc/framing.html .
*/
package ogg

import (
	"bytes"
	"encoding/binary"
	"hash/crc32"
	"io"
)

const MIMEType = "application/ogg"

var ByteOrder = binary.LittleEndian

type pageHeader struct {
	OggS          [4]byte // 0-3, always == "OggS"
	StreamVersion byte    // 4, always == 0
	HeaderType    byte    // 5
	Granule       int64   // 6-13, codec-specific
	Serial        uint32  // 14-17, associated with a logical stream
	Page          uint32  // 18-21, sequence number of page in packet
	Crc           uint32  // 22-25
	Nsegs         byte    // 26
}

const (
	// Continuation of packet
	cop byte = 1 + iota
	// Beginning of stream
	bos = 1 << iota
	// End of stream
	eos = 1 << iota
)

var crcTable = crc32.MakeTable(0x04c11db7)

type Encoder struct {
	serial uint32
	w      io.Writer
}

func NewEncoder(id uint32, w io.Writer) *Encoder {
	return &Encoder{id, w}
}

func (w *Encoder) WriteBOS(granule int64, packet []byte) error {
	_, err := w.writePacket(bos, granule, packet)
	return err
}

func (w *Encoder) Write(granule int64, packet []byte) (int, error) {
	return w.writePacket(0, granule, packet)
}

func (w *Encoder) WriteEOS() error {
	_, err := w.writePacket(eos, 0, nil)
	return err
}

func (w *Encoder) writePacket(kind byte, granule int64, packet []byte) (int, error) {
	h := pageHeader{
		OggS:       [4]byte{'O', 'g', 'g', 'S'},
		HeaderType: kind,
		Serial:     w.serial,
		Granule: granule,
	}

	var err error
	n, m := 0, 0
	const mps = 255 * 255 // maximum 255 segments of 255 bytes in a page

	s := 0
	e := s + mps
	if e > len(packet) {
		e = len(packet)
	}
	page := packet[s:e]
	n, err = w.writePage(page, &h)
	if err != nil {
		return n, err
	}
	s = e

	last := (len(packet) / mps) * mps
	h.HeaderType &= cop
	for s < last {
		h.Page++
		e = s + mps
		page = packet[s:e]
		m, err = w.writePage(page, &h)
		n += m
		if err != nil {
			return n, err
		}
		s = e
	}

	if len(packet) > 0 {
		m, err = w.writePage(packet[last:], &h)
		n += m
	}
	return n, err
}

func (w *Encoder) writePage(page []byte, h *pageHeader) (int, error) {
	h.Nsegs = byte(len(page)/255 + 1)
	segtbl := make([]byte, h.Nsegs)
	for i := 0; i < len(segtbl)-1; i++ {
		segtbl[i] = 255
	}
	segtbl[len(segtbl)-1] = byte(len(page) % 255)

	var hb bytes.Buffer
	err := binary.Write(&hb, ByteOrder, &h)
	if err != nil {
		return 0, err
	}

	hb.Write(segtbl)
	hb.Write(page)

	bb := hb.Bytes()
	crc := crc32.Checksum(bb, crcTable)
	err = binary.Write(bytes.NewBuffer(bb[22:26]), ByteOrder, crc)
	if err != nil {
		return 0, nil
	}

	n64, err := hb.WriteTo(w.w)
	return int(n64), err
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. 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 fuse

import (
	"fmt"
	"syscall"
	"unsafe"

	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/internal/buffer"
	"github.com/jacobsa/fuse/internal/fusekernel"
)

// Return the response that should be sent to the kernel. If the op requires no
// response, return a nil response.
func kernelResponse(
	fuseID uint64,
	op interface{},
	opErr error,
	protocol fusekernel.Protocol) (msg []byte) {
	// If the user replied with an error, create room enough just for the result
	// header and fill it in with an error. Otherwise create an appropriate
	// response.
	var b buffer.OutMessage
	if opErr != nil {
		b = buffer.NewOutMessage(0)
		if errno, ok := opErr.(syscall.Errno); ok {
			b.OutHeader().Error = -int32(errno)
		} else {
			b.OutHeader().Error = -int32(syscall.EIO)
		}
	} else {
		b = kernelResponseForOp(op, protocol)
	}

	msg = b.Bytes()

	// Fill in the rest of the header, if a response is required.
	if msg != nil {
		h := b.OutHeader()
		h.Unique = fuseID
		h.Len = uint32(len(msg))
	}

	return
}

// Like kernelResponse, but assumes the user replied with a nil error to the
// op. Returns a nil response if no response is required.
func kernelResponseForOp(
	op interface{},
	protocol fusekernel.Protocol) (b buffer.OutMessage) {
	// Create the appropriate output message
	switch o := op.(type) {
	case *fuseops.LookUpInodeOp:
		size := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.EntryOut)(b.Grow(size))
		convertChildInodeEntry(&o.Entry, out)

	case *fuseops.GetInodeAttributesOp:
		size := fusekernel.AttrOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.AttrOut)(b.Grow(size))
		out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration)
		convertAttributes(o.Inode, &o.Attributes, &out.Attr)

	case *fuseops.SetInodeAttributesOp:
		size := fusekernel.AttrOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.AttrOut)(b.Grow(size))
		out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration)
		convertAttributes(o.Inode, &o.Attributes, &out.Attr)

	case *fuseops.ForgetInodeOp:
		// No response.

	case *fuseops.MkDirOp:
		size := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.EntryOut)(b.Grow(size))
		convertChildInodeEntry(&o.Entry, out)

	case *fuseops.CreateFileOp:
		eSize := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{}))

		e := (*fusekernel.EntryOut)(b.Grow(eSize))
		convertChildInodeEntry(&o.Entry, e)

		oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
		oo.Fh = uint64(o.Handle)

	case *fuseops.CreateSymlinkOp:
		size := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.EntryOut)(b.Grow(size))
		convertChildInodeEntry(&o.Entry, out)

	case *fuseops.RenameOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.RmDirOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.UnlinkOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.OpenDirOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
		out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
		out.Fh = uint64(o.Handle)

	case *fuseops.ReadDirOp:
		b = buffer.NewOutMessage(uintptr(len(o.Data)))
		b.Append(o.Data)

	case *fuseops.ReleaseDirHandleOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.OpenFileOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
		out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
		out.Fh = uint64(o.Handle)

	case *fuseops.ReadFileOp:
		b = buffer.NewOutMessage(uintptr(len(o.Data)))
		b.Append(o.Data)

	case *fuseops.WriteFileOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{}))
		out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
		out.Size = uint32(len(o.Data))

	case *fuseops.SyncFileOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.FlushFileOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.ReleaseFileHandleOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.ReadSymlinkOp:
		b = buffer.NewOutMessage(uintptr(len(o.Target)))
		b.AppendString(o.Target)

	case *statFSOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{}))
		b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))

	case *interruptOp:
		// No response.

	case *initOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.InitOut{}))
		out := (*fusekernel.InitOut)(b.Grow(unsafe.Sizeof(fusekernel.InitOut{})))

		out.Major = o.Library.Major
		out.Minor = o.Library.Minor
		out.MaxReadahead = o.MaxReadahead
		out.Flags = uint32(o.Flags)
		out.MaxWrite = o.MaxWrite

	default:
		panic(fmt.Sprintf("Unknown op: %#v", op))
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Internal
////////////////////////////////////////////////////////////////////////

// A sentinel used for unknown ops. The user is expected to respond with a
// non-nil error.
type unknownOp struct {
	opCode uint32
	inode  fuseops.InodeID
}

type statFSOp struct {
}

type interruptOp struct {
	FuseID uint64
}

type initOp struct {
	// In
	Kernel fusekernel.Protocol

	// Out
	Library      fusekernel.Protocol
	MaxReadahead uint32
	Flags        fusekernel.InitFlags
	MaxWrite     uint32
}
<commit_msg>Fixed some long lines.<commit_after>// Copyright 2015 Google Inc. 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 fuse

import (
	"fmt"
	"syscall"
	"unsafe"

	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/internal/buffer"
	"github.com/jacobsa/fuse/internal/fusekernel"
)

// Return the response that should be sent to the kernel. If the op requires no
// response, return a nil response.
func kernelResponse(
	fuseID uint64,
	op interface{},
	opErr error,
	protocol fusekernel.Protocol) (msg []byte) {
	// If the user replied with an error, create room enough just for the result
	// header and fill it in with an error. Otherwise create an appropriate
	// response.
	var b buffer.OutMessage
	if opErr != nil {
		b = buffer.NewOutMessage(0)
		if errno, ok := opErr.(syscall.Errno); ok {
			b.OutHeader().Error = -int32(errno)
		} else {
			b.OutHeader().Error = -int32(syscall.EIO)
		}
	} else {
		b = kernelResponseForOp(op, protocol)
	}

	msg = b.Bytes()

	// Fill in the rest of the header, if a response is required.
	if msg != nil {
		h := b.OutHeader()
		h.Unique = fuseID
		h.Len = uint32(len(msg))
	}

	return
}

// Like kernelResponse, but assumes the user replied with a nil error to the
// op. Returns a nil response if no response is required.
func kernelResponseForOp(
	op interface{},
	protocol fusekernel.Protocol) (b buffer.OutMessage) {
	// Create the appropriate output message
	switch o := op.(type) {
	case *fuseops.LookUpInodeOp:
		size := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.EntryOut)(b.Grow(size))
		convertChildInodeEntry(&o.Entry, out)

	case *fuseops.GetInodeAttributesOp:
		size := fusekernel.AttrOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.AttrOut)(b.Grow(size))
		out.AttrValid, out.AttrValidNsec = convertExpirationTime(
			o.AttributesExpiration)
		convertAttributes(o.Inode, &o.Attributes, &out.Attr)

	case *fuseops.SetInodeAttributesOp:
		size := fusekernel.AttrOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.AttrOut)(b.Grow(size))
		out.AttrValid, out.AttrValidNsec = convertExpirationTime(
			o.AttributesExpiration)
		convertAttributes(o.Inode, &o.Attributes, &out.Attr)

	case *fuseops.ForgetInodeOp:
		// No response.

	case *fuseops.MkDirOp:
		size := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.EntryOut)(b.Grow(size))
		convertChildInodeEntry(&o.Entry, out)

	case *fuseops.CreateFileOp:
		eSize := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{}))

		e := (*fusekernel.EntryOut)(b.Grow(eSize))
		convertChildInodeEntry(&o.Entry, e)

		oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
		oo.Fh = uint64(o.Handle)

	case *fuseops.CreateSymlinkOp:
		size := fusekernel.EntryOutSize(protocol)
		b = buffer.NewOutMessage(size)
		out := (*fusekernel.EntryOut)(b.Grow(size))
		convertChildInodeEntry(&o.Entry, out)

	case *fuseops.RenameOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.RmDirOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.UnlinkOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.OpenDirOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
		out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
		out.Fh = uint64(o.Handle)

	case *fuseops.ReadDirOp:
		b = buffer.NewOutMessage(uintptr(len(o.Data)))
		b.Append(o.Data)

	case *fuseops.ReleaseDirHandleOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.OpenFileOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
		out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
		out.Fh = uint64(o.Handle)

	case *fuseops.ReadFileOp:
		b = buffer.NewOutMessage(uintptr(len(o.Data)))
		b.Append(o.Data)

	case *fuseops.WriteFileOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{}))
		out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
		out.Size = uint32(len(o.Data))

	case *fuseops.SyncFileOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.FlushFileOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.ReleaseFileHandleOp:
		b = buffer.NewOutMessage(0)

	case *fuseops.ReadSymlinkOp:
		b = buffer.NewOutMessage(uintptr(len(o.Target)))
		b.AppendString(o.Target)

	case *statFSOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{}))
		b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))

	case *interruptOp:
		// No response.

	case *initOp:
		b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.InitOut{}))
		out := (*fusekernel.InitOut)(b.Grow(unsafe.Sizeof(fusekernel.InitOut{})))

		out.Major = o.Library.Major
		out.Minor = o.Library.Minor
		out.MaxReadahead = o.MaxReadahead
		out.Flags = uint32(o.Flags)
		out.MaxWrite = o.MaxWrite

	default:
		panic(fmt.Sprintf("Unknown op: %#v", op))
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Internal
////////////////////////////////////////////////////////////////////////

// A sentinel used for unknown ops. The user is expected to respond with a
// non-nil error.
type unknownOp struct {
	opCode uint32
	inode  fuseops.InodeID
}

type statFSOp struct {
}

type interruptOp struct {
	FuseID uint64
}

type initOp struct {
	// In
	Kernel fusekernel.Protocol

	// Out
	Library      fusekernel.Protocol
	MaxReadahead uint32
	Flags        fusekernel.InitFlags
	MaxWrite     uint32
}
<|endoftext|>


<commit_before>// Copyright 2010-2012 The W32 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package w32

/*
#include <windows.h>

typedef struct _PDH_FMT_COUNTERVALUE {
  DWORD  CStatus;
  double doubleValue;
} PDH_FMT_COUNTERVALUE, *PPDH_FMT_COUNTERVALUE;

typedef struct _PDH_FMT_COUNTERVALUE_ITEM {
  LPTSTR               szName;
  PDH_FMT_COUNTERVALUE FmtValue;
} PDH_FMT_COUNTERVALUE_ITEM, *PPDH_FMT_COUNTERVALUE_ITEM;

PDH_FMT_COUNTERVALUE_ITEM* getCounterValueBuffer(DWORD buffersize){
	 PDH_FMT_COUNTERVALUE_ITEM *pItems = NULL;
	 pItems = (PDH_FMT_COUNTERVALUE_ITEM *) malloc(buffersize);

	return pItems;
}
*/
import "C"

import (
	"fmt"
	"reflect"
	"syscall"
	"unsafe"
)

const (
	PDH_SUCCESS = 0x00
	// Status codes returned from PDH functions
	PDH_MORE_DATA                 = 0x800007D2
	PDH_CSTATUS_BAD_COUNTERNAME   = 0xC0000BC0
	PDH_CSTATUS_NO_COUNTER        = 0xC0000BB9
	PDH_CSTATUS_NO_COUNTERNAME    = 0xC0000BBF
	PDH_CSTATUS_NO_INSTANCE       = 0x800007D1
	PDH_CSTATUS_NO_MACHINE        = 0x800007D0
	PDH_CSTATUS_NO_OBJECT         = 0xC0000BB8
	PDH_FUNCTION_NOT_FOUND        = 0xC0000BBE
	PDH_INVALID_ARGUMENT          = 0xC0000BBD
	PDH_INVALID_HANDLE            = 0xC0000BBC
	PDH_MEMORY_ALLOCATION_FAILURE = 0xC0000BBB

	// Argument options
	PERF_DETAIL_NOVICE   = 100
	PERF_DETAIL_ADVANCED = 200
	PERF_DETAIL_EXPERT   = 300
	PERF_DETAIL_WIZARD   = 400

	// Counter Formats options
	PDH_FMT_LONG   = 0x00000100 // long integer
	PDH_FMT_DOUBLE = 0x00000200 // double precision float
	PDH_FMT_LARGE  = 0x00000400 // 64-bit integer

	PDH_FMT_NOSCALE = 0x00001000 // Do not apply the counter's default scaling factor.
	PDH_FMT_1000    = 0x00002000 // Multiply the actual value by 1,000.

	/* Counter values greater than 100 (for example,
	   counter values measuring the processor load on multiprocessor computers)
	   will not be reset to 100. The default behavior is that
	   counter values are capped at a value of 100. */
	PDH_FMT_NOCAP100 = 0x00008000
)

var (
	modpdh = syscall.NewLazyDLL("pdh.dll")

	procPdhEnumObjects              = modpdh.NewProc("PdhEnumObjectsA")
	procPdhEnumObjectItems          = modpdh.NewProc("PdhEnumObjectItemsA")
	procPdhOpenQuery                = modpdh.NewProc("PdhOpenQueryA")
	procPdhCloseQuery                = modpdh.NewProc("PdhCloseQuery")
	procPdhAddCounter               = modpdh.NewProc("PdhAddCounterA")
	procPdhCollectQueryData         = modpdh.NewProc("PdhCollectQueryData")
	procPdhValidatePath             = modpdh.NewProc("PdhValidatePathA")
	procPdhGetFormattedCounterValue = modpdh.NewProc("PdhGetFormattedCounterValue")
	procPdhGetFormattedCounterArray = modpdh.NewProc("PdhGetFormattedCounterArrayA")
)

type PDH_ERROR struct {
	Code        uint32
	Description string
}

func (err *PDH_ERROR) Error() string {
	return err.Description
}

func (err *PDH_ERROR) String() string {
	return err.Description
}

func PdhErrorCode(sCode uint32) *PDH_ERROR {
	switch sCode {
	case 0:
		return nil
	case PDH_MORE_DATA:
		return &PDH_ERROR{Code: sCode,
			Description: "There is more data to return than would fit in the supplied buffer. Allocate a larger buffer and call the function again."}
	case PDH_CSTATUS_BAD_COUNTERNAME:
		return &PDH_ERROR{Code: sCode,
			Description: "Unable to parse the counter path. Check the format and syntax of the specified path."}
	case PDH_CSTATUS_NO_COUNTER:
		return &PDH_ERROR{Code: sCode,
			Description: "The specified counter could not be found."}
	case PDH_CSTATUS_NO_COUNTERNAME:
		return &PDH_ERROR{Code: sCode,
			Description: "No counter was specified."}
	case PDH_CSTATUS_NO_INSTANCE:
		return &PDH_ERROR{Code: sCode,
			Description: "The specified instance is not present."}
	case PDH_CSTATUS_NO_MACHINE:
		return &PDH_ERROR{Code: sCode,
			Description: "Unable to connect to the specified computer, or the computer is offline."}
	case PDH_CSTATUS_NO_OBJECT:
		return &PDH_ERROR{Code: sCode,
			Description: "The specified object is not found on the system."}
	case PDH_FUNCTION_NOT_FOUND:
		return &PDH_ERROR{Code: sCode,
			Description: "Unable to find the specified function."}
	case PDH_INVALID_ARGUMENT:
		return &PDH_ERROR{Code: sCode,
			Description: "A required argument is missing or incorrect."}
	case PDH_INVALID_HANDLE:
		return &PDH_ERROR{Code: sCode,
			Description: "The handle is not a valid PDH object."}
	case PDH_MEMORY_ALLOCATION_FAILURE:
		return &PDH_ERROR{Code: sCode,
			Description: "A PDH function could not allocate enough temporary memory to complete the operation. Close some applications or extend the page file and retry the function."}
	default:
		return &PDH_ERROR{Code: sCode, Description: fmt.Sprintf("Unimplemented error, code 0x%X", sCode)}
	}
}

func PdhEnumObjects(detailLevel uint32, refreshCache bool, bufferlen uint32) (uint32, []byte, uint32) {
	var buf []byte
	var buflen uint32
	var bufPtr *byte
	if bufferlen > 0 {
		buf = make([]byte, bufferlen+1)
		bufPtr = &buf[0]
		buf[bufferlen] = 0
		buflen = uint32(bufferlen)
	}

	refreshCacheWin := TRUE
	if !refreshCache {
		refreshCacheWin = FALSE
	}

	ret, _, _ := procPdhEnumObjects.Call(
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(bufPtr)),
		uintptr(unsafe.Pointer(&buflen)),
		uintptr(detailLevel),
		uintptr(refreshCacheWin),
	)

	if buflen > 0 && uint32(ret) == 0 {
		return uint32(ret), buf[:buflen-1], buflen
	}
	return uint32(ret), []byte{}, buflen
}

func PdhEnumObjectItems(object string, counterBufferLen, instanceBufferLen uint32,
	detailLevel uint32) (status uint32, outCountBuf []byte, outCountLen uint32,
	outInstBuf []byte, outInstLen uint32) {
	var counterBuf []byte
	var counterBufLen uint32
	var counterBufPtr *byte
	if counterBufferLen > 0 {
		counterBuf = make([]byte, counterBufferLen+1)
		counterBufPtr = &counterBuf[0]
		counterBuf[counterBufferLen] = 0
		counterBufLen = uint32(counterBufferLen)
	}

	var instanceBuf []byte
	var instanceBufLen uint32
	var instanceBufPtr *byte
	if instanceBufferLen > 0 {
		instanceBuf = make([]byte, instanceBufferLen+1)
		instanceBufPtr = &instanceBuf[0]
		instanceBuf[instanceBufferLen] = 0
		instanceBufLen = uint32(instanceBufferLen)
	}

	objName, _ := syscall.BytePtrFromString(object)
	ret, _, _ := procPdhEnumObjectItems.Call(
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(objName)),
		uintptr(unsafe.Pointer(counterBufPtr)),
		uintptr(unsafe.Pointer(&counterBufLen)),
		uintptr(unsafe.Pointer(instanceBufPtr)),
		uintptr(unsafe.Pointer(&instanceBufLen)),
		uintptr(detailLevel),
		uintptr(uint32(0)),
	)

	if uint32(ret) != 0 {
		return uint32(ret), []byte{}, counterBufLen, []byte{}, instanceBufLen
	}
	if counterBufLen > 0 {
		outCountBuf = counterBuf[:counterBufLen-1]
		outCountLen = counterBufLen
	}
	if instanceBufLen > 0 {
		outInstBuf = instanceBuf[:instanceBufLen-1]
		outInstLen = instanceBufLen
	}

	return uint32(ret), outCountBuf, outCountLen, outInstBuf, outInstLen
}

func PdhOpenQuery() (uint32, HANDLE) {
	var pdhQuery HANDLE
	var userData uintptr
	ret, _, _ := procPdhOpenQuery.Call(
		uintptr(0),
		uintptr(userData),
		uintptr(unsafe.Pointer(&pdhQuery)),
	)

	return uint32(ret), pdhQuery
}

func PdhCloseQuery(pdhQuery HANDLE) uint32 {
	ret, _, _ := procPdhCloseQuery.Call(
		uintptr(pdhQuery),
	)

	return uint32(ret)
}

func PdhAddCounter(pdhQuery HANDLE, counterPath string) (uint32, HANDLE) {
	var counter HANDLE
	var userData uintptr

	path, _ := syscall.BytePtrFromString(counterPath)

	ret, _, _ := procPdhAddCounter.Call(
		uintptr(pdhQuery),
		uintptr(unsafe.Pointer(path)),
		uintptr(userData),
		uintptr(unsafe.Pointer(&counter)),
	)

	return uint32(ret), counter
}

func PdhCollectQueryData(pdhQuery HANDLE) (uint32, HANDLE) {
	ret, _, _ := procPdhCollectQueryData.Call(
		uintptr(pdhQuery),
	)

	return uint32(ret), pdhQuery
}

func PdhGetFormattedCounterValue(hCounter HANDLE) (status uint32, outBuffer PDH_FMT_COUNTERVALUE) {
	counterType := uint32(0)
	var buffer C.PDH_FMT_COUNTERVALUE
	ret, _, _ := procPdhGetFormattedCounterValue.Call(
		uintptr(hCounter),
		uintptr(uint32(PDH_FMT_DOUBLE|PDH_FMT_NOSCALE)),
		uintptr(unsafe.Pointer(&counterType)),
		uintptr(unsafe.Pointer(&buffer)),
	)

	if status = uint32(ret); status != 0 {
		return
	}

	outBuffer = PDH_FMT_COUNTERVALUE{
		Status: uint32(buffer.CStatus),
		Value:  float64(buffer.doubleValue),
	}

	return
}

func PdhGetFormattedCounterArray(hCounter HANDLE) (status uint32,
	respLen uint32, numItems uint32, outBuffer []PDH_FMT_COUNTERVALUE_ITEM) {

	// Call once to get buffer size
	ret, _, _ := procPdhGetFormattedCounterArray.Call(
		uintptr(hCounter),
		uintptr(uint32(PDH_FMT_DOUBLE|PDH_FMT_NOSCALE)),
		uintptr(unsafe.Pointer(&respLen)),
		uintptr(unsafe.Pointer(&numItems)),
		uintptr(0),
	)

	if status = uint32(ret); status != PDH_MORE_DATA {
		return
	}

	itemBuffer := C.getCounterValueBuffer(C.DWORD(respLen))
	defer C.free(unsafe.Pointer(itemBuffer))

	ret, _, _ = procPdhGetFormattedCounterArray.Call(
		uintptr(hCounter),
		uintptr(uint32(PDH_FMT_DOUBLE|PDH_FMT_NOSCALE)),
		uintptr(unsafe.Pointer(&respLen)),
		uintptr(unsafe.Pointer(&numItems)),
		uintptr(unsafe.Pointer(itemBuffer)),
	)

	if status = uint32(ret); status != 0 {
		return
	}

	bufferArray := *(*[]C.PDH_FMT_COUNTERVALUE_ITEM)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(itemBuffer)),
		Len:  int(numItems),
		Cap:  int(numItems),
	}))

	outBuffer = make([]PDH_FMT_COUNTERVALUE_ITEM, numItems)
	// Loop through the array and print the instance name and counter value.
	for i := uint32(0); i < numItems; i++ {
		outBuffer[i].Name = C.GoString((*C.char)(unsafe.Pointer(bufferArray[i].szName)))
		outBuffer[i].Item = PDH_FMT_COUNTERVALUE{
			Status: uint32(bufferArray[i].FmtValue.CStatus),
			Value:  float64(bufferArray[i].FmtValue.doubleValue),
		}
	}

	return
}

func PdhValidatePath(processCounterPath string) uint32 {
	path, _ := syscall.BytePtrFromString(processCounterPath)

	ret, _, _ := procPdhValidatePath.Call(
		uintptr(unsafe.Pointer(path)),
	)

	return uint32(ret)
}
<commit_msg>Fix response type in PdhErrorCode<commit_after>// Copyright 2010-2012 The W32 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package w32

/*
#include <windows.h>

typedef struct _PDH_FMT_COUNTERVALUE {
  DWORD  CStatus;
  double doubleValue;
} PDH_FMT_COUNTERVALUE, *PPDH_FMT_COUNTERVALUE;

typedef struct _PDH_FMT_COUNTERVALUE_ITEM {
  LPTSTR               szName;
  PDH_FMT_COUNTERVALUE FmtValue;
} PDH_FMT_COUNTERVALUE_ITEM, *PPDH_FMT_COUNTERVALUE_ITEM;

PDH_FMT_COUNTERVALUE_ITEM* getCounterValueBuffer(DWORD buffersize){
	 PDH_FMT_COUNTERVALUE_ITEM *pItems = NULL;
	 pItems = (PDH_FMT_COUNTERVALUE_ITEM *) malloc(buffersize);

	return pItems;
}
*/
import "C"

import (
	"fmt"
	"reflect"
	"syscall"
	"unsafe"
)

const (
	PDH_SUCCESS = 0x00
	// Status codes returned from PDH functions
	PDH_MORE_DATA                 = 0x800007D2
	PDH_CSTATUS_BAD_COUNTERNAME   = 0xC0000BC0
	PDH_CSTATUS_NO_COUNTER        = 0xC0000BB9
	PDH_CSTATUS_NO_COUNTERNAME    = 0xC0000BBF
	PDH_CSTATUS_NO_INSTANCE       = 0x800007D1
	PDH_CSTATUS_NO_MACHINE        = 0x800007D0
	PDH_CSTATUS_NO_OBJECT         = 0xC0000BB8
	PDH_FUNCTION_NOT_FOUND        = 0xC0000BBE
	PDH_INVALID_ARGUMENT          = 0xC0000BBD
	PDH_INVALID_HANDLE            = 0xC0000BBC
	PDH_MEMORY_ALLOCATION_FAILURE = 0xC0000BBB

	// Argument options
	PERF_DETAIL_NOVICE   = 100
	PERF_DETAIL_ADVANCED = 200
	PERF_DETAIL_EXPERT   = 300
	PERF_DETAIL_WIZARD   = 400

	// Counter Formats options
	PDH_FMT_LONG   = 0x00000100 // long integer
	PDH_FMT_DOUBLE = 0x00000200 // double precision float
	PDH_FMT_LARGE  = 0x00000400 // 64-bit integer

	PDH_FMT_NOSCALE = 0x00001000 // Do not apply the counter's default scaling factor.
	PDH_FMT_1000    = 0x00002000 // Multiply the actual value by 1,000.

	/* Counter values greater than 100 (for example,
	   counter values measuring the processor load on multiprocessor computers)
	   will not be reset to 100. The default behavior is that
	   counter values are capped at a value of 100. */
	PDH_FMT_NOCAP100 = 0x00008000
)

var (
	modpdh = syscall.NewLazyDLL("pdh.dll")

	procPdhEnumObjects              = modpdh.NewProc("PdhEnumObjectsA")
	procPdhEnumObjectItems          = modpdh.NewProc("PdhEnumObjectItemsA")
	procPdhOpenQuery                = modpdh.NewProc("PdhOpenQueryA")
	procPdhCloseQuery                = modpdh.NewProc("PdhCloseQuery")
	procPdhAddCounter               = modpdh.NewProc("PdhAddCounterA")
	procPdhCollectQueryData         = modpdh.NewProc("PdhCollectQueryData")
	procPdhValidatePath             = modpdh.NewProc("PdhValidatePathA")
	procPdhGetFormattedCounterValue = modpdh.NewProc("PdhGetFormattedCounterValue")
	procPdhGetFormattedCounterArray = modpdh.NewProc("PdhGetFormattedCounterArrayA")
)

type PDH_ERROR struct {
	Code        uint32
	Description string
}

func (err *PDH_ERROR) Error() string {
	return err.Description
}

func (err *PDH_ERROR) String() string {
	return err.Description
}

func PdhErrorCode(sCode uint32) error {
	switch sCode {
	case 0:
		return nil
	case PDH_MORE_DATA:
		return &PDH_ERROR{Code: sCode,
			Description: "There is more data to return than would fit in the supplied buffer. Allocate a larger buffer and call the function again."}
	case PDH_CSTATUS_BAD_COUNTERNAME:
		return &PDH_ERROR{Code: sCode,
			Description: "Unable to parse the counter path. Check the format and syntax of the specified path."}
	case PDH_CSTATUS_NO_COUNTER:
		return &PDH_ERROR{Code: sCode,
			Description: "The specified counter could not be found."}
	case PDH_CSTATUS_NO_COUNTERNAME:
		return &PDH_ERROR{Code: sCode,
			Description: "No counter was specified."}
	case PDH_CSTATUS_NO_INSTANCE:
		return &PDH_ERROR{Code: sCode,
			Description: "The specified instance is not present."}
	case PDH_CSTATUS_NO_MACHINE:
		return &PDH_ERROR{Code: sCode,
			Description: "Unable to connect to the specified computer, or the computer is offline."}
	case PDH_CSTATUS_NO_OBJECT:
		return &PDH_ERROR{Code: sCode,
			Description: "The specified object is not found on the system."}
	case PDH_FUNCTION_NOT_FOUND:
		return &PDH_ERROR{Code: sCode,
			Description: "Unable to find the specified function."}
	case PDH_INVALID_ARGUMENT:
		return &PDH_ERROR{Code: sCode,
			Description: "A required argument is missing or incorrect."}
	case PDH_INVALID_HANDLE:
		return &PDH_ERROR{Code: sCode,
			Description: "The handle is not a valid PDH object."}
	case PDH_MEMORY_ALLOCATION_FAILURE:
		return &PDH_ERROR{Code: sCode,
			Description: "A PDH function could not allocate enough temporary memory to complete the operation. Close some applications or extend the page file and retry the function."}
	default:
		return &PDH_ERROR{Code: sCode, Description: fmt.Sprintf("Unimplemented error, code 0x%X", sCode)}
	}
}

func PdhEnumObjects(detailLevel uint32, refreshCache bool, bufferlen uint32) (uint32, []byte, uint32) {
	var buf []byte
	var buflen uint32
	var bufPtr *byte
	if bufferlen > 0 {
		buf = make([]byte, bufferlen+1)
		bufPtr = &buf[0]
		buf[bufferlen] = 0
		buflen = uint32(bufferlen)
	}

	refreshCacheWin := TRUE
	if !refreshCache {
		refreshCacheWin = FALSE
	}

	ret, _, _ := procPdhEnumObjects.Call(
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(bufPtr)),
		uintptr(unsafe.Pointer(&buflen)),
		uintptr(detailLevel),
		uintptr(refreshCacheWin),
	)

	if buflen > 0 && uint32(ret) == 0 {
		return uint32(ret), buf[:buflen-1], buflen
	}
	return uint32(ret), []byte{}, buflen
}

func PdhEnumObjectItems(object string, counterBufferLen, instanceBufferLen uint32,
	detailLevel uint32) (status uint32, outCountBuf []byte, outCountLen uint32,
	outInstBuf []byte, outInstLen uint32) {
	var counterBuf []byte
	var counterBufLen uint32
	var counterBufPtr *byte
	if counterBufferLen > 0 {
		counterBuf = make([]byte, counterBufferLen+1)
		counterBufPtr = &counterBuf[0]
		counterBuf[counterBufferLen] = 0
		counterBufLen = uint32(counterBufferLen)
	}

	var instanceBuf []byte
	var instanceBufLen uint32
	var instanceBufPtr *byte
	if instanceBufferLen > 0 {
		instanceBuf = make([]byte, instanceBufferLen+1)
		instanceBufPtr = &instanceBuf[0]
		instanceBuf[instanceBufferLen] = 0
		instanceBufLen = uint32(instanceBufferLen)
	}

	objName, _ := syscall.BytePtrFromString(object)
	ret, _, _ := procPdhEnumObjectItems.Call(
		uintptr(0),
		uintptr(0),
		uintptr(unsafe.Pointer(objName)),
		uintptr(unsafe.Pointer(counterBufPtr)),
		uintptr(unsafe.Pointer(&counterBufLen)),
		uintptr(unsafe.Pointer(instanceBufPtr)),
		uintptr(unsafe.Pointer(&instanceBufLen)),
		uintptr(detailLevel),
		uintptr(uint32(0)),
	)

	if uint32(ret) != 0 {
		return uint32(ret), []byte{}, counterBufLen, []byte{}, instanceBufLen
	}
	if counterBufLen > 0 {
		outCountBuf = counterBuf[:counterBufLen-1]
		outCountLen = counterBufLen
	}
	if instanceBufLen > 0 {
		outInstBuf = instanceBuf[:instanceBufLen-1]
		outInstLen = instanceBufLen
	}

	return uint32(ret), outCountBuf, outCountLen, outInstBuf, outInstLen
}

func PdhOpenQuery() (uint32, HANDLE) {
	var pdhQuery HANDLE
	var userData uintptr
	ret, _, _ := procPdhOpenQuery.Call(
		uintptr(0),
		uintptr(userData),
		uintptr(unsafe.Pointer(&pdhQuery)),
	)

	return uint32(ret), pdhQuery
}

func PdhCloseQuery(pdhQuery HANDLE) uint32 {
	ret, _, _ := procPdhCloseQuery.Call(
		uintptr(pdhQuery),
	)

	return uint32(ret)
}

func PdhAddCounter(pdhQuery HANDLE, counterPath string) (uint32, HANDLE) {
	var counter HANDLE
	var userData uintptr

	path, _ := syscall.BytePtrFromString(counterPath)

	ret, _, _ := procPdhAddCounter.Call(
		uintptr(pdhQuery),
		uintptr(unsafe.Pointer(path)),
		uintptr(userData),
		uintptr(unsafe.Pointer(&counter)),
	)

	return uint32(ret), counter
}

func PdhCollectQueryData(pdhQuery HANDLE) (uint32, HANDLE) {
	ret, _, _ := procPdhCollectQueryData.Call(
		uintptr(pdhQuery),
	)

	return uint32(ret), pdhQuery
}

func PdhGetFormattedCounterValue(hCounter HANDLE) (status uint32, outBuffer PDH_FMT_COUNTERVALUE) {
	counterType := uint32(0)
	var buffer C.PDH_FMT_COUNTERVALUE
	ret, _, _ := procPdhGetFormattedCounterValue.Call(
		uintptr(hCounter),
		uintptr(uint32(PDH_FMT_DOUBLE|PDH_FMT_NOSCALE)),
		uintptr(unsafe.Pointer(&counterType)),
		uintptr(unsafe.Pointer(&buffer)),
	)

	if status = uint32(ret); status != 0 {
		return
	}

	outBuffer = PDH_FMT_COUNTERVALUE{
		Status: uint32(buffer.CStatus),
		Value:  float64(buffer.doubleValue),
	}

	return
}

func PdhGetFormattedCounterArray(hCounter HANDLE) (status uint32,
	respLen uint32, numItems uint32, outBuffer []PDH_FMT_COUNTERVALUE_ITEM) {

	// Call once to get buffer size
	ret, _, _ := procPdhGetFormattedCounterArray.Call(
		uintptr(hCounter),
		uintptr(uint32(PDH_FMT_DOUBLE|PDH_FMT_NOSCALE)),
		uintptr(unsafe.Pointer(&respLen)),
		uintptr(unsafe.Pointer(&numItems)),
		uintptr(0),
	)

	if status = uint32(ret); status != PDH_MORE_DATA {
		return
	}

	itemBuffer := C.getCounterValueBuffer(C.DWORD(respLen))
	defer C.free(unsafe.Pointer(itemBuffer))

	ret, _, _ = procPdhGetFormattedCounterArray.Call(
		uintptr(hCounter),
		uintptr(uint32(PDH_FMT_DOUBLE|PDH_FMT_NOSCALE)),
		uintptr(unsafe.Pointer(&respLen)),
		uintptr(unsafe.Pointer(&numItems)),
		uintptr(unsafe.Pointer(itemBuffer)),
	)

	if status = uint32(ret); status != 0 {
		return
	}

	bufferArray := *(*[]C.PDH_FMT_COUNTERVALUE_ITEM)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(itemBuffer)),
		Len:  int(numItems),
		Cap:  int(numItems),
	}))

	outBuffer = make([]PDH_FMT_COUNTERVALUE_ITEM, numItems)
	// Loop through the array and print the instance name and counter value.
	for i := uint32(0); i < numItems; i++ {
		outBuffer[i].Name = C.GoString((*C.char)(unsafe.Pointer(bufferArray[i].szName)))
		outBuffer[i].Item = PDH_FMT_COUNTERVALUE{
			Status: uint32(bufferArray[i].FmtValue.CStatus),
			Value:  float64(bufferArray[i].FmtValue.doubleValue),
		}
	}

	return
}

func PdhValidatePath(processCounterPath string) uint32 {
	path, _ := syscall.BytePtrFromString(processCounterPath)

	ret, _, _ := procPdhValidatePath.Call(
		uintptr(unsafe.Pointer(path)),
	)

	return uint32(ret)
}
<|endoftext|>


<commit_before>package ThermostatLogic

import (
	"fmt"
	"time"

	"github.com/gaeshi-gaeshi/thermo-smart/HeatersController"
)

// Simple is very simple logic used to control the heaters based on the current and target temperatures
func Simple(targetTemperature float32, getCurrentTemperature func() float32, printCurrentTemperature func(float32)) {
	for {
		currentTemperature := getCurrentTemperature()

		temperatureDifference := targetTemperature - currentTemperature
		if temperatureDifference > 1 {
			HeatersController.SetNumberOfWorkingHeaters(3)
		} else if temperatureDifference <= 1 && temperatureDifference > 0 {
			HeatersController.SetNumberOfWorkingHeaters(2)
		} else if temperatureDifference <= 0 && temperatureDifference > -1 {
			HeatersController.SetNumberOfWorkingHeaters(1)
		} else {
			HeatersController.SetNumberOfWorkingHeaters(0)
		}

		printCurrentTemperature(currentTemperature)
		fmt.Printf("Currently working heaters - %d\n", HeatersController.GetNumberOfWorkingHeaters())

		time.Sleep(time.Minute)
	}
}

// Complex uses complex logic to control the temperature. It keeps 1 heaters always on.
// Also Ton != Toff for the other 2 heaters.
func Complex(targetTemperature float32, getCurrentTemperature func() float32, printCurrentTemperature func(float32)) {
	HeatersController.SetNumberOfWorkingHeaters(1)

	for {
		currentTemperature := getCurrentTemperature()

		temperatureDifference := targetTemperature - currentTemperature
		if shouldIncreaseTargetTemperature() {
			temperatureDifference++
		}

		if temperatureDifference >= 1 {
			HeatersController.SetNumberOfWorkingHeaters(3)
		} else if temperatureDifference < 1 && temperatureDifference >= 0.5 {
			HeatersController.SetNumberOfWorkingHeaters(2)
		} else if temperatureDifference <= -0.5 {
			HeatersController.SetNumberOfWorkingHeaters(1)
		}

		printCurrentTemperature(currentTemperature)
		fmt.Printf("Currently working heaters - %d\n", HeatersController.GetNumberOfWorkingHeaters())

		time.Sleep(time.Minute * 5)
	}
}

func shouldIncreaseTargetTemperature() bool {
	now := time.Now()
	if now.Hour() == 23 && now.Minute() >= 30 {
		return true
	} else if now.Hour() < 6 {
		return true
	} else if now.Hour() == 6 && now.Minute() <= 30 {
		return true
	}

	return false
}
<commit_msg>Changed logic for keeping 1 heater always on. It wouldn't be effective during hot days<commit_after>package ThermostatLogic

import (
	"fmt"
	"time"

	"github.com/gaeshi-gaeshi/thermo-smart/HeatersController"
)

// Simple is very simple logic used to control the heaters based on the current and target temperatures
func Simple(targetTemperature float32, getCurrentTemperature func() float32, printCurrentTemperature func(float32)) {
	for {
		currentTemperature := getCurrentTemperature()

		temperatureDifference := targetTemperature - currentTemperature
		if temperatureDifference > 1 {
			HeatersController.SetNumberOfWorkingHeaters(3)
		} else if temperatureDifference <= 1 && temperatureDifference > 0 {
			HeatersController.SetNumberOfWorkingHeaters(2)
		} else if temperatureDifference <= 0 && temperatureDifference > -1 {
			HeatersController.SetNumberOfWorkingHeaters(1)
		} else {
			HeatersController.SetNumberOfWorkingHeaters(0)
		}

		printCurrentTemperature(currentTemperature)
		fmt.Printf("Currently working heaters - %d\n", HeatersController.GetNumberOfWorkingHeaters())

		time.Sleep(time.Minute)
	}
}

// Complex uses complex logic to control the temperature. It keeps 1 heater for longer period of time.
// Also Ton != Toff for the 3 heaters.
func Complex(targetTemperature float32, getCurrentTemperature func() float32, printCurrentTemperature func(float32)) {
	for {
		currentTemperature := getCurrentTemperature()

		temperatureDifference := targetTemperature - currentTemperature
		if shouldIncreaseTargetTemperature() {
			temperatureDifference++
			fmt.Println("Target temperature increased by 1 degree.")
		}

		if temperatureDifference >= 1 {
			HeatersController.SetNumberOfWorkingHeaters(3)
		} else if temperatureDifference < 1 && temperatureDifference >= 0.5 {
			HeatersController.SetNumberOfWorkingHeaters(2)
		} else if temperatureDifference <= -0.5 && temperatureDifference > -2 {
			HeatersController.SetNumberOfWorkingHeaters(1)
		} else if temperatureDifference <= -2 {
			HeatersController.SetNumberOfWorkingHeaters(0)
		}

		printCurrentTemperature(currentTemperature)
		fmt.Printf("Currently working heaters - %d\n", HeatersController.GetNumberOfWorkingHeaters())

		time.Sleep(time.Minute * 5)
	}
}

func shouldIncreaseTargetTemperature() bool {
	now := time.Now()
	if now.Hour() == 23 && now.Minute() >= 30 {
		return true
	} else if now.Hour() < 6 {
		return true
	} else if now.Hour() == 6 && now.Minute() <= 30 {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package cloudns

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/miekg/dns/dnsutil"

	"github.com/StackExchange/dnscontrol/v3/models"
	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
	"github.com/StackExchange/dnscontrol/v3/providers"
)

/*
ClouDNS API DNS provider:
Info required in `creds.json`:
   - auth-id or sub-auth-id
   - auth-password
*/

// NewCloudns creates the provider.
func NewCloudns(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
	c := &cloudnsProvider{}

	c.creds.id, c.creds.password, c.creds.subid = m["auth-id"], m["auth-password"], m["sub-auth-id"]

	if (c.creds.id == "" && c.creds.subid == "") || c.creds.password == "" {
		return nil, fmt.Errorf("missing ClouDNS auth-id or sub-auth-id and auth-password")
	}

	// Get a domain to validate authentication
	if err := c.fetchDomainList(); err != nil {
		return nil, err
	}

	return c, nil
}

var features = providers.DocumentationNotes{
	//providers.CanUseDS:               providers.Can(), // in ClouDNS we can add  DS record just for a subdomain(child)
	providers.CanGetZones:            providers.Can(),
	providers.CanUseAlias:            providers.Can(),
	providers.CanUseCAA:              providers.Can(),
	providers.CanUseDSForChildren:    providers.Can(),
	providers.CanUsePTR:              providers.Can(),
	providers.CanUseSRV:              providers.Can(),
	providers.CanUseSSHFP:            providers.Can(),
	providers.CanUseTLSA:             providers.Can(),
	providers.DocCreateDomains:       providers.Can(),
	providers.DocDualHost:            providers.Unimplemented(),
	providers.DocOfficiallySupported: providers.Cannot(),
}

func init() {
	fns := providers.DspFuncs{
		Initializer:   NewCloudns,
		RecordAuditor: AuditRecords,
	}
	providers.RegisterDomainServiceProviderType("CLOUDNS", fns, features)
	providers.RegisterCustomRecordType("CLOUDNS_WR", "CLOUDNS", "WR")
}

// GetNameservers returns the nameservers for a domain.
func (c *cloudnsProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
	if len(c.nameserversNames) == 0 {
		c.fetchAvailableNameservers()
	}
	return models.ToNameservers(c.nameserversNames)
}

// GetDomainCorrections returns the corrections for a domain.
func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	dc, err := dc.Copy()
	if err != nil {
		return nil, err
	}

	dc.Punycode()

	if c.domainIndex == nil {
		if err := c.fetchDomainList(); err != nil {
			return nil, err
		}
	}
	domainID, ok := c.domainIndex[dc.Name]
	if !ok {
		return nil, fmt.Errorf("'%s' not a zone in ClouDNS account", dc.Name)
	}

	existingRecords, err := c.GetZoneRecords(dc.Name)
	if err != nil {
		return nil, err
	}
	// Normalize
	models.PostProcessRecords(existingRecords)

	// Get a list of available TTL values.
	// The TTL list needs to be obtained for each domain, so get it first here.
	c.fetchAvailableTTLValues(dc.Name)
	// ClouDNS can only be specified from a specific TTL list, so change the TTL in advance.
	for _, record := range dc.Records {
		record.TTL = fixTTL(record.TTL)
	}

	differ := diff.New(dc)
	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
	if err != nil {
		return nil, err
	}

	var corrections []*models.Correction

	// Deletes first so changing type works etc.
	for _, m := range del {
		id := m.Existing.Original.(*domainRecord).ID
		corr := &models.Correction{
			Msg: fmt.Sprintf("%s, ClouDNS ID: %s", m.String(), id),
			F: func() error {
				return c.deleteRecord(domainID, id)
			},
		}
		// at ClouDNS, we MUST have a NS for a DS
		// So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error
		if m.Existing.Type == "DS" {
			// type DS is prepended - so executed first
			corrections = append([]*models.Correction{corr}, corrections...)
		} else {
			corrections = append(corrections, corr)
		}
	}

	var createCorrections []*models.Correction
	for _, m := range create {
		req, err := toReq(m.Desired)
		if err != nil {
			return nil, err
		}

		corr := &models.Correction{
			Msg: m.String(),
			F: func() error {
				return c.createRecord(domainID, req)
			},
		}
		// at ClouDNS, we MUST have a NS for a DS
		// So, when creating, we must create the NS first, otherwise creating the DS throws an error
		if m.Desired.Type == "NS" {
			// type NS is prepended - so executed first
			createCorrections = append([]*models.Correction{corr}, createCorrections...)
		} else {
			createCorrections = append(createCorrections, corr)
		}
	}
	corrections = append(corrections, createCorrections...)

	for _, m := range modify {
		id := m.Existing.Original.(*domainRecord).ID
		req, err := toReq(m.Desired)
		if err != nil {
			return nil, err
		}

		// ClouDNS does not require the trailing period to be specified when updating an NS record where the A or AAAA record exists in the zone.
		// So, modify it to remove the trailing period.
		if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
			req["record"] = strings.TrimSuffix(req["record"], ".")
		}

		corr := &models.Correction{
			Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id),
			F: func() error {
				return c.modifyRecord(domainID, id, req)
			},
		}
		corrections = append(corrections, corr)
	}

	return corrections, nil
}

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (c *cloudnsProvider) GetZoneRecords(domain string) (models.Records, error) {
	records, err := c.getRecords(domain)
	if err != nil {
		return nil, err
	}
	existingRecords := make([]*models.RecordConfig, len(records))
	for i := range records {
		existingRecords[i] = toRc(domain, &records[i])
	}
	return existingRecords, nil
}

// EnsureDomainExists returns an error if domain doesn't exist.
func (c *cloudnsProvider) EnsureDomainExists(domain string) error {
	if err := c.fetchDomainList(); err != nil {
		return err
	}
	// domain already exists
	if _, ok := c.domainIndex[domain]; ok {
		return nil
	}
	return c.createDomain(domain)
}

//parses the ClouDNS format into our standard RecordConfig
func toRc(domain string, r *domainRecord) *models.RecordConfig {

	ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
	priority, _ := strconv.ParseUint(r.Priority, 10, 16)
	weight, _ := strconv.ParseUint(r.Weight, 10, 16)
	port, _ := strconv.ParseUint(r.Port, 10, 16)

	rc := &models.RecordConfig{
		Type:         r.Type,
		TTL:          uint32(ttl),
		MxPreference: uint16(priority),
		SrvPriority:  uint16(priority),
		SrvWeight:    uint16(weight),
		SrvPort:      uint16(port),
		Original:     r,
	}
	rc.SetLabel(r.Host, domain)

	switch rtype := r.Type; rtype { // #rtype_variations
	case "TXT":
		rc.SetTargetTXT(r.Target)
	case "CNAME", "MX", "NS", "SRV", "ALIAS", "PTR":
		rc.SetTarget(dnsutil.AddOrigin(r.Target+".", domain))
	case "CAA":
		caaFlag, _ := strconv.ParseUint(r.CaaFlag, 10, 8)
		rc.CaaFlag = uint8(caaFlag)
		rc.CaaTag = r.CaaTag
		rc.SetTarget(r.CaaValue)
	case "TLSA":
		tlsaUsage, _ := strconv.ParseUint(r.TlsaUsage, 10, 8)
		rc.TlsaUsage = uint8(tlsaUsage)
		tlsaSelector, _ := strconv.ParseUint(r.TlsaSelector, 10, 8)
		rc.TlsaSelector = uint8(tlsaSelector)
		tlsaMatchingType, _ := strconv.ParseUint(r.TlsaMatchingType, 10, 8)
		rc.TlsaMatchingType = uint8(tlsaMatchingType)
		rc.SetTarget(r.Target)
	case "SSHFP":
		sshfpAlgorithm, _ := strconv.ParseUint(r.SshfpAlgorithm, 10, 8)
		rc.SshfpAlgorithm = uint8(sshfpAlgorithm)
		sshfpFingerprint, _ := strconv.ParseUint(r.SshfpFingerprint, 10, 8)
		rc.SshfpFingerprint = uint8(sshfpFingerprint)
		rc.SetTarget(r.Target)
	case "DS":
		dsKeyTag, _ := strconv.ParseUint(r.DsKeyTag, 10, 16)
		rc.DsKeyTag = uint16(dsKeyTag)
		dsAlgorithm, _ := strconv.ParseUint(r.SshfpAlgorithm, 10, 8) // SshFpAlgorithm and DsAlgorithm both use json field "algorithm"
		rc.DsAlgorithm = uint8(dsAlgorithm)
		dsDigestType, _ := strconv.ParseUint(r.DsDigestType, 10, 8)
		rc.DsDigestType = uint8(dsDigestType)
		rc.DsDigest = r.Target
		rc.SetTarget(r.Target)
	default:
		rc.SetTarget(r.Target)
	}

	return rc
}

//toReq takes a RecordConfig and turns it into the native format used by the API.
func toReq(rc *models.RecordConfig) (requestParams, error) {
	req := requestParams{
		"record-type": rc.Type,
		"host":        rc.GetLabel(),
		"record":      rc.GetTargetField(),
		"ttl":         strconv.Itoa(int(rc.TTL)),
	}

	// ClouDNS doesn't use "@", it uses an empty name
	if req["host"] == "@" {
		req["host"] = ""
	}

	switch rc.Type { // #rtype_variations
	case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "ALIAS", "CNAME", "WR":
		// Nothing special.
	case "MX":
		req["priority"] = strconv.Itoa(int(rc.MxPreference))
	case "SRV":
		req["priority"] = strconv.Itoa(int(rc.SrvPriority))
		req["weight"] = strconv.Itoa(int(rc.SrvWeight))
		req["port"] = strconv.Itoa(int(rc.SrvPort))
	case "CAA":
		req["caa_flag"] = strconv.Itoa(int(rc.CaaFlag))
		req["caa_type"] = rc.CaaTag
		req["caa_value"] = rc.GetTargetField()
	case "TLSA":
		req["tlsa_usage"] = strconv.Itoa(int(rc.TlsaUsage))
		req["tlsa_selector"] = strconv.Itoa(int(rc.TlsaSelector))
		req["tlsa_matching_type"] = strconv.Itoa(int(rc.TlsaMatchingType))
	case "SSHFP":
		req["algorithm"] = strconv.Itoa(int(rc.SshfpAlgorithm))
		req["fptype"] = strconv.Itoa(int(rc.SshfpFingerprint))
	case "DS":
		req["key-tag"] = strconv.Itoa(int(rc.DsKeyTag))
		req["algorithm"] = strconv.Itoa(int(rc.DsAlgorithm))
		req["digest-type"] = strconv.Itoa(int(rc.DsDigestType))
		req["record"] = rc.DsDigest
	default:
		return nil, fmt.Errorf("ClouDNS.toReq rtype %q unimplemented", rc.Type)
	}

	return req, nil
}
<commit_msg>CLOUDNS: Fix creation of intra-zone NS records (see #1263) (#1469)<commit_after>package cloudns

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"github.com/miekg/dns/dnsutil"

	"github.com/StackExchange/dnscontrol/v3/models"
	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
	"github.com/StackExchange/dnscontrol/v3/providers"
)

/*
ClouDNS API DNS provider:
Info required in `creds.json`:
   - auth-id or sub-auth-id
   - auth-password
*/

// NewCloudns creates the provider.
func NewCloudns(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
	c := &cloudnsProvider{}

	c.creds.id, c.creds.password, c.creds.subid = m["auth-id"], m["auth-password"], m["sub-auth-id"]

	if (c.creds.id == "" && c.creds.subid == "") || c.creds.password == "" {
		return nil, fmt.Errorf("missing ClouDNS auth-id or sub-auth-id and auth-password")
	}

	// Get a domain to validate authentication
	if err := c.fetchDomainList(); err != nil {
		return nil, err
	}

	return c, nil
}

var features = providers.DocumentationNotes{
	//providers.CanUseDS:               providers.Can(), // in ClouDNS we can add  DS record just for a subdomain(child)
	providers.CanGetZones:            providers.Can(),
	providers.CanUseAlias:            providers.Can(),
	providers.CanUseCAA:              providers.Can(),
	providers.CanUseDSForChildren:    providers.Can(),
	providers.CanUsePTR:              providers.Can(),
	providers.CanUseSRV:              providers.Can(),
	providers.CanUseSSHFP:            providers.Can(),
	providers.CanUseTLSA:             providers.Can(),
	providers.DocCreateDomains:       providers.Can(),
	providers.DocDualHost:            providers.Unimplemented(),
	providers.DocOfficiallySupported: providers.Cannot(),
}

func init() {
	fns := providers.DspFuncs{
		Initializer:   NewCloudns,
		RecordAuditor: AuditRecords,
	}
	providers.RegisterDomainServiceProviderType("CLOUDNS", fns, features)
	providers.RegisterCustomRecordType("CLOUDNS_WR", "CLOUDNS", "WR")
}

// GetNameservers returns the nameservers for a domain.
func (c *cloudnsProvider) GetNameservers(domain string) ([]*models.Nameserver, error) {
	if len(c.nameserversNames) == 0 {
		c.fetchAvailableNameservers()
	}
	return models.ToNameservers(c.nameserversNames)
}

// GetDomainCorrections returns the corrections for a domain.
func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	dc, err := dc.Copy()
	if err != nil {
		return nil, err
	}

	dc.Punycode()

	if c.domainIndex == nil {
		if err := c.fetchDomainList(); err != nil {
			return nil, err
		}
	}
	domainID, ok := c.domainIndex[dc.Name]
	if !ok {
		return nil, fmt.Errorf("'%s' not a zone in ClouDNS account", dc.Name)
	}

	existingRecords, err := c.GetZoneRecords(dc.Name)
	if err != nil {
		return nil, err
	}
	// Normalize
	models.PostProcessRecords(existingRecords)

	// Get a list of available TTL values.
	// The TTL list needs to be obtained for each domain, so get it first here.
	c.fetchAvailableTTLValues(dc.Name)
	// ClouDNS can only be specified from a specific TTL list, so change the TTL in advance.
	for _, record := range dc.Records {
		record.TTL = fixTTL(record.TTL)
	}

	differ := diff.New(dc)
	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
	if err != nil {
		return nil, err
	}

	var corrections []*models.Correction

	// Deletes first so changing type works etc.
	for _, m := range del {
		id := m.Existing.Original.(*domainRecord).ID
		corr := &models.Correction{
			Msg: fmt.Sprintf("%s, ClouDNS ID: %s", m.String(), id),
			F: func() error {
				return c.deleteRecord(domainID, id)
			},
		}
		// at ClouDNS, we MUST have a NS for a DS
		// So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error
		if m.Existing.Type == "DS" {
			// type DS is prepended - so executed first
			corrections = append([]*models.Correction{corr}, corrections...)
		} else {
			corrections = append(corrections, corr)
		}
	}

	var createCorrections []*models.Correction
	for _, m := range create {
		req, err := toReq(m.Desired)
		if err != nil {
			return nil, err
		}

		// ClouDNS does not require the trailing period to be specified when creating an NS record where the A or AAAA record exists in the zone.
		// So, modify it to remove the trailing period.
		if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
			req["record"] = strings.TrimSuffix(req["record"], ".")
		}

		corr := &models.Correction{
			Msg: m.String(),
			F: func() error {
				return c.createRecord(domainID, req)
			},
		}
		// at ClouDNS, we MUST have a NS for a DS
		// So, when creating, we must create the NS first, otherwise creating the DS throws an error
		if m.Desired.Type == "NS" {
			// type NS is prepended - so executed first
			createCorrections = append([]*models.Correction{corr}, createCorrections...)
		} else {
			createCorrections = append(createCorrections, corr)
		}
	}
	corrections = append(corrections, createCorrections...)

	for _, m := range modify {
		id := m.Existing.Original.(*domainRecord).ID
		req, err := toReq(m.Desired)
		if err != nil {
			return nil, err
		}

		// ClouDNS does not require the trailing period to be specified when updating an NS record where the A or AAAA record exists in the zone.
		// So, modify it to remove the trailing period.
		if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
			req["record"] = strings.TrimSuffix(req["record"], ".")
		}

		corr := &models.Correction{
			Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id),
			F: func() error {
				return c.modifyRecord(domainID, id, req)
			},
		}
		corrections = append(corrections, corr)
	}

	return corrections, nil
}

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (c *cloudnsProvider) GetZoneRecords(domain string) (models.Records, error) {
	records, err := c.getRecords(domain)
	if err != nil {
		return nil, err
	}
	existingRecords := make([]*models.RecordConfig, len(records))
	for i := range records {
		existingRecords[i] = toRc(domain, &records[i])
	}
	return existingRecords, nil
}

// EnsureDomainExists returns an error if domain doesn't exist.
func (c *cloudnsProvider) EnsureDomainExists(domain string) error {
	if err := c.fetchDomainList(); err != nil {
		return err
	}
	// domain already exists
	if _, ok := c.domainIndex[domain]; ok {
		return nil
	}
	return c.createDomain(domain)
}

//parses the ClouDNS format into our standard RecordConfig
func toRc(domain string, r *domainRecord) *models.RecordConfig {

	ttl, _ := strconv.ParseUint(r.TTL, 10, 32)
	priority, _ := strconv.ParseUint(r.Priority, 10, 16)
	weight, _ := strconv.ParseUint(r.Weight, 10, 16)
	port, _ := strconv.ParseUint(r.Port, 10, 16)

	rc := &models.RecordConfig{
		Type:         r.Type,
		TTL:          uint32(ttl),
		MxPreference: uint16(priority),
		SrvPriority:  uint16(priority),
		SrvWeight:    uint16(weight),
		SrvPort:      uint16(port),
		Original:     r,
	}
	rc.SetLabel(r.Host, domain)

	switch rtype := r.Type; rtype { // #rtype_variations
	case "TXT":
		rc.SetTargetTXT(r.Target)
	case "CNAME", "MX", "NS", "SRV", "ALIAS", "PTR":
		rc.SetTarget(dnsutil.AddOrigin(r.Target+".", domain))
	case "CAA":
		caaFlag, _ := strconv.ParseUint(r.CaaFlag, 10, 8)
		rc.CaaFlag = uint8(caaFlag)
		rc.CaaTag = r.CaaTag
		rc.SetTarget(r.CaaValue)
	case "TLSA":
		tlsaUsage, _ := strconv.ParseUint(r.TlsaUsage, 10, 8)
		rc.TlsaUsage = uint8(tlsaUsage)
		tlsaSelector, _ := strconv.ParseUint(r.TlsaSelector, 10, 8)
		rc.TlsaSelector = uint8(tlsaSelector)
		tlsaMatchingType, _ := strconv.ParseUint(r.TlsaMatchingType, 10, 8)
		rc.TlsaMatchingType = uint8(tlsaMatchingType)
		rc.SetTarget(r.Target)
	case "SSHFP":
		sshfpAlgorithm, _ := strconv.ParseUint(r.SshfpAlgorithm, 10, 8)
		rc.SshfpAlgorithm = uint8(sshfpAlgorithm)
		sshfpFingerprint, _ := strconv.ParseUint(r.SshfpFingerprint, 10, 8)
		rc.SshfpFingerprint = uint8(sshfpFingerprint)
		rc.SetTarget(r.Target)
	case "DS":
		dsKeyTag, _ := strconv.ParseUint(r.DsKeyTag, 10, 16)
		rc.DsKeyTag = uint16(dsKeyTag)
		dsAlgorithm, _ := strconv.ParseUint(r.SshfpAlgorithm, 10, 8) // SshFpAlgorithm and DsAlgorithm both use json field "algorithm"
		rc.DsAlgorithm = uint8(dsAlgorithm)
		dsDigestType, _ := strconv.ParseUint(r.DsDigestType, 10, 8)
		rc.DsDigestType = uint8(dsDigestType)
		rc.DsDigest = r.Target
		rc.SetTarget(r.Target)
	default:
		rc.SetTarget(r.Target)
	}

	return rc
}

//toReq takes a RecordConfig and turns it into the native format used by the API.
func toReq(rc *models.RecordConfig) (requestParams, error) {
	req := requestParams{
		"record-type": rc.Type,
		"host":        rc.GetLabel(),
		"record":      rc.GetTargetField(),
		"ttl":         strconv.Itoa(int(rc.TTL)),
	}

	// ClouDNS doesn't use "@", it uses an empty name
	if req["host"] == "@" {
		req["host"] = ""
	}

	switch rc.Type { // #rtype_variations
	case "A", "AAAA", "NS", "PTR", "TXT", "SOA", "ALIAS", "CNAME", "WR":
		// Nothing special.
	case "MX":
		req["priority"] = strconv.Itoa(int(rc.MxPreference))
	case "SRV":
		req["priority"] = strconv.Itoa(int(rc.SrvPriority))
		req["weight"] = strconv.Itoa(int(rc.SrvWeight))
		req["port"] = strconv.Itoa(int(rc.SrvPort))
	case "CAA":
		req["caa_flag"] = strconv.Itoa(int(rc.CaaFlag))
		req["caa_type"] = rc.CaaTag
		req["caa_value"] = rc.GetTargetField()
	case "TLSA":
		req["tlsa_usage"] = strconv.Itoa(int(rc.TlsaUsage))
		req["tlsa_selector"] = strconv.Itoa(int(rc.TlsaSelector))
		req["tlsa_matching_type"] = strconv.Itoa(int(rc.TlsaMatchingType))
	case "SSHFP":
		req["algorithm"] = strconv.Itoa(int(rc.SshfpAlgorithm))
		req["fptype"] = strconv.Itoa(int(rc.SshfpFingerprint))
	case "DS":
		req["key-tag"] = strconv.Itoa(int(rc.DsKeyTag))
		req["algorithm"] = strconv.Itoa(int(rc.DsAlgorithm))
		req["digest-type"] = strconv.Itoa(int(rc.DsDigestType))
		req["record"] = rc.DsDigest
	default:
		return nil, fmt.Errorf("ClouDNS.toReq rtype %q unimplemented", rc.Type)
	}

	return req, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"html"
	"io/ioutil"
	"net/http"
	"os"
	"os/user"
	"regexp"
	"strings"

	"github.com/imwally/pinboard"
)

var (
	options   = flag.NewFlagSet("", flag.ExitOnError)
	privFlag  = options.Bool("private", false, "private bookmark")
	readFlag  = options.Bool("readlater", false, "read later bookmark")
	longFlag  = options.Bool("l", false, "display long format")
	extFlag   = options.String("text", "", "longer description of bookmark")
	tagFlag   = options.String("tag", "", "space delimited tags for bookmark")
	titleFlag = options.String("title", "", "title of the bookmark")

	token string
)

var usage = `Usage: pin
  pin rm  URL
  pin add URL [OPTION]
  pin ls [OPTION]

Options:
  -title      title of bookmark being added
  -tag        space delimited tags
  -private    mark bookmark as private
  -readlater  mark bookmark as read later
  -text       longer description of bookmark
  -l          long format for ls
`

// COUNT is the number of bookmarks to display.
const COUNT int = 50

// Piped is a helper function to check for piped input. It will return
// input, true if data was piped.
func Piped() (string, bool) {
	fi, err := os.Stdin.Stat()
	if err != nil {
		fmt.Fprintf(os.Stderr, "pin: %s", err)
		return "", false
	}

	isPipe := (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe
	if isPipe {
		read := bufio.NewReader(os.Stdin)
		line, _, err := read.ReadLine()
		if err != nil {
			fmt.Fprintf(os.Stderr, "pin: %s", err)
			return "", false
		}
		return string(line), true
	}

	return "", false
}

// PageTitle attempts to parse an HTML document for the <title> tag
// using the regexp package. If no title is found then the url itself
// is returned as the title.
func PageTitle(url string) (title string, err error) {
	// Check first to see if this is an HTML document.
	head, err := http.Head(url)
	if err != nil {
		return "", err
	}

	// This is not an HTML document, using URL as title.
	if !strings.Contains(head.Header["Content-Type"][0], "text/html") {
		return url, nil
	}

	// Carry on getting the HTML document.
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	// A regular expression that searches for any characters or
	// new lines within the bounds of <title> and </title>.
	re := regexp.MustCompile("<title>(?s)(.*?)(?s)</title>")
	t := string(re.FindSubmatch(body)[1])

	// No title found, using URL as title.
	if len(t) < 1 {
		return url, nil
	}

	// Trim new lines and white spaces from title.
	t = strings.TrimSpace(t)

	return html.UnescapeString(t), nil
}

// Add checks flag values and encodes the GET URL for adding a bookmark.
func Add(p *pinboard.Post) {
	var args []string

	// Check if URL is piped in or first argument. Optional tags
	// should follow the URL.
	url, piped := Piped()

	if piped {
		p.URL = url
		args = flag.Args()[1:]
	}

	if !piped {
		// Check length of arguments.
		err := CheckArgs(flag.Args())
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			return
		}
		p.URL = flag.Args()[1]
		args = flag.Args()[2:]
	}

	// Parse flags after the URL.
	options.Parse(args)

	if *titleFlag != "" {
		p.Description = *titleFlag
	} else {
		// Use page title if title flag is not supplied.
		title, err := PageTitle(p.URL)
		if err != nil {
			fmt.Fprintf(os.Stderr, "pin: %s: %s\n", err, p.URL)
			return
		}

		p.Description = title
	}

	if *privFlag {
		p.Shared = "no"
	}

	if *readFlag {
		p.Toread = "yes"
	}

	p.Extended = *extFlag
	p.Tags = *tagFlag

	err := p.Add()
	if err != nil {
		fmt.Fprintf(os.Stderr, "pin: %s\n", err)
	}
}

// Delete will delete the URL specified.
func Delete(p *pinboard.Post) {
	// Check if URL is piped in or first argument.
	url, piped := Piped()

	if piped {
		p.URL = url
	}

	if !piped {
		// Check length of arguments.
		err := CheckArgs(flag.Args())
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			return
		}
		p.URL = flag.Args()[1]
	}

	err := p.Delete()
	if err != nil {
		fmt.Fprintf(os.Stderr, "pin: %s\n", err)
	}
}

// Show will list the most recent bookmarks. The -tag and -readlater
// flags can be used to filter results.
func Show(p *pinboard.Post) {
	args := flag.Args()[1:]
	options.Parse(args)

	if *tagFlag != "" {
		p.Tag = *tagFlag
	}
	if *readFlag {
		p.Toread = "yes"
	}

	p.Count = COUNT

	recent := p.ShowRecent()
	for _, v := range recent.Posts {
		if *longFlag {
			if v.Shared == "no" {
				fmt.Printf("[*]")
			}
			if v.Toread == "yes" {
				fmt.Printf("[#]")
			}
			fmt.Println(v.Description)
			fmt.Println(v.Href)
			if v.Extended != "" {
				fmt.Println(v.Extended)
			}
			if v.Tags != "" {
				fmt.Println(v.Tags)
			}
			fmt.Println()
		} else {
			fmt.Println(v.Href)
		}
	}
}

// Help prints pin's usage text.
func Help(p *pinboard.Post) {
	fmt.Printf("%s", usage)
}

// Start takes a map of command names to functions, parses flag
// arguments, initialises a new pinboard.Post and runs the command if
// found.
func Start(cmds map[string]func(p *pinboard.Post)) {
	flag.Parse()
	if flag.NArg() < 1 {
		fmt.Fprintf(os.Stderr, "pin: no command is given.\n")
		return
	}

	cmdName := flag.Arg(0)
	cmd, ok := cmds[cmdName]
	if !ok {
		fmt.Fprintf(os.Stderr, "pin: command %s not found.\n", cmdName)
		return
	}

	// Display help without creating a new Post or setting the user's
	// token.
	if cmdName == "help" {
		cmd(nil)
		return
	}

	// Initialise a new Pinboard post.
	p := new(pinboard.Post)

	// Check and set user's token.
	token, err := GetToken()
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		return
	}
	p.Token = token

	cmd(p)
}

// CheckArgs makes sure enough arguments are given to sub commands.
func CheckArgs(args []string) error {
	if len(args) < 2 {
		return errors.New("pin: not enough arguments given.\n")
	}

	return nil
}

// GetToken attempts to read a user's token from ~/.pinboard.
func GetToken() (string, error) {
	u, err := user.Current()
	if err != nil {
		return "", err
	}

	content, err := ioutil.ReadFile(u.HomeDir + "/.pinboard")
	if err != nil {
		return "", errors.New("pin: No authorization token found. Please add your authorization token to ~/.pinboard\n")
	}

	return string(content), nil
}

func main() {
	cmds := map[string]func(*pinboard.Post){
		"help": Help,
		"add":  Add,
		"rm":   Delete,
		"ls":   Show,
	}

	Start(cmds)
}
<commit_msg>Check length of title regex slice before indexing it<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"html"
	"io/ioutil"
	"net/http"
	"os"
	"os/user"
	"regexp"
	"strings"

	"github.com/imwally/pinboard"
)

var (
	options   = flag.NewFlagSet("", flag.ExitOnError)
	privFlag  = options.Bool("private", false, "private bookmark")
	readFlag  = options.Bool("readlater", false, "read later bookmark")
	longFlag  = options.Bool("l", false, "display long format")
	extFlag   = options.String("text", "", "longer description of bookmark")
	tagFlag   = options.String("tag", "", "space delimited tags for bookmark")
	titleFlag = options.String("title", "", "title of the bookmark")

	token string
)

var usage = `Usage: pin
  pin rm  URL
  pin add URL [OPTION]
  pin ls [OPTION]

Options:
  -title      title of bookmark being added
  -tag        space delimited tags
  -private    mark bookmark as private
  -readlater  mark bookmark as read later
  -text       longer description of bookmark
  -l          long format for ls
`

// COUNT is the number of bookmarks to display.
const COUNT int = 50

// Piped is a helper function to check for piped input. It will return
// input, true if data was piped.
func Piped() (string, bool) {
	fi, err := os.Stdin.Stat()
	if err != nil {
		fmt.Fprintf(os.Stderr, "pin: %s", err)
		return "", false
	}

	isPipe := (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe
	if isPipe {
		read := bufio.NewReader(os.Stdin)
		line, _, err := read.ReadLine()
		if err != nil {
			fmt.Fprintf(os.Stderr, "pin: %s", err)
			return "", false
		}
		return string(line), true
	}

	return "", false
}

// PageTitle attempts to parse an HTML document for the <title> tag
// using the regexp package. If no title is found then the url itself
// is returned as the title.
func PageTitle(url string) (title string, err error) {
	// Check first to see if this is an HTML document.
	head, err := http.Head(url)
	if err != nil {
		return "", err
	}

	// This is not an HTML document, using URL as title.
	if !strings.Contains(head.Header["Content-Type"][0], "text/html") {
		return url, nil
	}

	// Carry on getting the HTML document.
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	// A regular expression that searches for any characters or
	// new lines within the bounds of <title> and </title>.
	re := regexp.MustCompile("<title>(?s)(.*?)(?s)</title>")
	match := re.FindSubmatch(body)

	// No title found, using URL as title.
	if len(match) < 1 {
		return url, nil
	}

	// Trim new lines and white spaces from title.
	t := strings.TrimSpace(string(match[1]))

	return html.UnescapeString(t), nil
}

// Add checks flag values and encodes the GET URL for adding a bookmark.
func Add(p *pinboard.Post) {
	var args []string

	// Check if URL is piped in or first argument. Optional tags
	// should follow the URL.
	url, piped := Piped()

	if piped {
		p.URL = url
		args = flag.Args()[1:]
	}

	if !piped {
		// Check length of arguments.
		err := CheckArgs(flag.Args())
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			return
		}
		p.URL = flag.Args()[1]
		args = flag.Args()[2:]
	}

	// Parse flags after the URL.
	options.Parse(args)

	if *titleFlag != "" {
		p.Description = *titleFlag
	} else {
		// Use page title if title flag is not supplied.
		title, err := PageTitle(p.URL)
		if err != nil {
			fmt.Fprintf(os.Stderr, "pin: %s: %s\n", err, p.URL)
			return
		}

		p.Description = title
	}

	if *privFlag {
		p.Shared = "no"
	}

	if *readFlag {
		p.Toread = "yes"
	}

	p.Extended = *extFlag
	p.Tags = *tagFlag

	err := p.Add()
	if err != nil {
		fmt.Fprintf(os.Stderr, "pin: %s\n", err)
	}
}

// Delete will delete the URL specified.
func Delete(p *pinboard.Post) {
	// Check if URL is piped in or first argument.
	url, piped := Piped()

	if piped {
		p.URL = url
	}

	if !piped {
		// Check length of arguments.
		err := CheckArgs(flag.Args())
		if err != nil {
			fmt.Fprintf(os.Stderr, err.Error())
			return
		}
		p.URL = flag.Args()[1]
	}

	err := p.Delete()
	if err != nil {
		fmt.Fprintf(os.Stderr, "pin: %s\n", err)
	}
}

// Show will list the most recent bookmarks. The -tag and -readlater
// flags can be used to filter results.
func Show(p *pinboard.Post) {
	args := flag.Args()[1:]
	options.Parse(args)

	if *tagFlag != "" {
		p.Tag = *tagFlag
	}
	if *readFlag {
		p.Toread = "yes"
	}

	p.Count = COUNT

	recent := p.ShowRecent()
	for _, v := range recent.Posts {
		if *longFlag {
			if v.Shared == "no" {
				fmt.Printf("[*]")
			}
			if v.Toread == "yes" {
				fmt.Printf("[#]")
			}
			fmt.Println(v.Description)
			fmt.Println(v.Href)
			if v.Extended != "" {
				fmt.Println(v.Extended)
			}
			if v.Tags != "" {
				fmt.Println(v.Tags)
			}
			fmt.Println()
		} else {
			fmt.Println(v.Href)
		}
	}
}

// Help prints pin's usage text.
func Help(p *pinboard.Post) {
	fmt.Printf("%s", usage)
}

// Start takes a map of command names to functions, parses flag
// arguments, initialises a new pinboard.Post and runs the command if
// found.
func Start(cmds map[string]func(p *pinboard.Post)) {
	flag.Parse()
	if flag.NArg() < 1 {
		fmt.Fprintf(os.Stderr, "pin: no command is given.\n")
		return
	}

	cmdName := flag.Arg(0)
	cmd, ok := cmds[cmdName]
	if !ok {
		fmt.Fprintf(os.Stderr, "pin: command %s not found.\n", cmdName)
		return
	}

	// Display help without creating a new Post or setting the user's
	// token.
	if cmdName == "help" {
		cmd(nil)
		return
	}

	// Initialise a new Pinboard post.
	p := new(pinboard.Post)

	// Check and set user's token.
	token, err := GetToken()
	if err != nil {
		fmt.Fprintf(os.Stderr, err.Error())
		return
	}
	p.Token = token

	cmd(p)
}

// CheckArgs makes sure enough arguments are given to sub commands.
func CheckArgs(args []string) error {
	if len(args) < 2 {
		return errors.New("pin: not enough arguments given.\n")
	}

	return nil
}

// GetToken attempts to read a user's token from ~/.pinboard.
func GetToken() (string, error) {
	u, err := user.Current()
	if err != nil {
		return "", err
	}

	content, err := ioutil.ReadFile(u.HomeDir + "/.pinboard")
	if err != nil {
		return "", errors.New("pin: No authorization token found. Please add your authorization token to ~/.pinboard\n")
	}

	return string(content), nil
}

func main() {
	cmds := map[string]func(*pinboard.Post){
		"help": Help,
		"add":  Add,
		"rm":   Delete,
		"ls":   Show,
	}

	Start(cmds)
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 spyglass

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/url"
	"path"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"github.com/sirupsen/logrus"
	"google.golang.org/api/iterator"

	"k8s.io/test-infra/prow/spyglass/viewers"
	"k8s.io/test-infra/testgrid/util/gcs"
)

const (
	httpScheme  = "http"
	httpsScheme = "https"
)

var (
	// ErrCannotParseSource is returned by newGCSJobSource when an incorrectly formatted source string is passed
	ErrCannotParseSource = errors.New("could not create job source from provided source")
)

// GCSArtifactFetcher contains information used for fetching artifacts from GCS
type GCSArtifactFetcher struct {
	client *storage.Client
}

// gcsJobSource is a location in GCS where Prow job-specific artifacts are stored. This implementation assumes
// Prow's native GCS upload format (treating GCS keys as a directory structure), and is not
// intended to support arbitrary GCS bucket upload formats.
type gcsJobSource struct {
	source     string
	linkPrefix string
	bucket     string
	jobPrefix  string
	jobName    string
	buildID    string
}

// NewGCSArtifactFetcher creates a new ArtifactFetcher with a real GCS Client
func NewGCSArtifactFetcher(c *storage.Client) *GCSArtifactFetcher {
	return &GCSArtifactFetcher{
		client: c,
	}
}

func fieldsForJob(src *gcsJobSource) logrus.Fields {
	return logrus.Fields{
		"jobPrefix": src.jobPath(),
	}
}

// newGCSJobSource creates a new gcsJobSource from a given bucket and jobPrefix
func newGCSJobSource(src string) (*gcsJobSource, error) {
	gcsURL, err := url.Parse(fmt.Sprintf("gs://%s", src))
	if err != nil {
		return &gcsJobSource{}, ErrCannotParseSource
	}
	gcsPath := &gcs.Path{}
	err = gcsPath.SetURL(gcsURL)
	if err != nil {
		return &gcsJobSource{}, ErrCannotParseSource
	}

	tokens := strings.FieldsFunc(gcsPath.Object(), func(c rune) bool { return c == '/' })
	if len(tokens) < 2 {
		return &gcsJobSource{}, ErrCannotParseSource
	}
	buildID := tokens[len(tokens)-1]
	name := tokens[len(tokens)-2]
	return &gcsJobSource{
		source:     src,
		linkPrefix: "gs://",
		bucket:     gcsPath.Bucket(),
		jobPrefix:  path.Clean(gcsPath.Object()) + "/",
		jobName:    name,
		buildID:    buildID,
	}, nil
}

// Artifacts lists all artifacts available for the given job source
func (af *GCSArtifactFetcher) artifacts(key string) ([]string, error) {
	src, err := newGCSJobSource(key)
	if err != nil {
		return nil, fmt.Errorf("Failed to get GCS job source from %s: %v", key, err)
	}

	listStart := time.Now()
	bucketName, prefix := extractBucketPrefixPair(src.jobPath())
	artifacts := []string{}
	bkt := af.client.Bucket(bucketName)
	q := storage.Query{
		Prefix:   prefix,
		Versions: false,
	}
	objIter := bkt.Objects(context.Background(), &q)
	wait := []time.Duration{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}
	for i := 0; ; {
		oAttrs, err := objIter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			logrus.WithFields(fieldsForJob(src)).WithError(err).Error("Error accessing GCS artifact.")
			if i >= len(wait) {
				return artifacts, fmt.Errorf("timed out: error accessing GCS artifact: %v", err)
			}
			time.Sleep(wait[i] * time.Millisecond)
			i++
			continue
		}
		artifacts = append(artifacts, strings.TrimPrefix(oAttrs.Name, prefix))
		i = 0
	}
	listElapsed := time.Since(listStart)
	logrus.WithField("duration", listElapsed).Infof("Listed %d artifacts.", len(artifacts))
	return artifacts, nil
}

type gcsArtifactHandle struct {
	*storage.ObjectHandle
}

func (h *gcsArtifactHandle) NewReader(ctx context.Context) (io.ReadCloser, error) {
	return h.ObjectHandle.NewReader(ctx)
}

func (h *gcsArtifactHandle) NewRangeReader(ctx context.Context, offset, length int64) (io.ReadCloser, error) {
	return h.ObjectHandle.NewRangeReader(ctx, offset, length)
}

// Artifact contructs a GCS artifact from the given GCS bucket and key. Uses the golang GCS library
// to get read handles. If the artifactName is not a valid key in the bucket a handle will still be
// constructed and returned, but all read operations will fail (dictated by behavior of golang GCS lib).
func (af *GCSArtifactFetcher) artifact(key string, artifactName string, sizeLimit int64) (viewers.Artifact, error) {
	src, err := newGCSJobSource(key)
	if err != nil {
		return nil, fmt.Errorf("Failed to get GCS job source from %s: %v", key, err)
	}

	bucketName, prefix := extractBucketPrefixPair(src.jobPath())
	bkt := af.client.Bucket(bucketName)
	obj := &gcsArtifactHandle{bkt.Object(path.Join(prefix, artifactName))}
	artifactLink := &url.URL{
		Scheme: httpsScheme,
		Host:   "storage.googleapis.com",
		Path:   path.Join(src.jobPath(), artifactName),
	}
	return NewGCSArtifact(context.Background(), obj, artifactLink.String(), artifactName, sizeLimit), nil
}

func extractBucketPrefixPair(gcsPath string) (string, string) {
	split := strings.SplitN(gcsPath, "/", 2)
	return split[0], split[1]
}

// CanonicalLink gets a link to the location of job-specific artifacts in GCS
func (src *gcsJobSource) canonicalLink() string {
	return path.Join(src.linkPrefix, src.bucket, src.jobPrefix)
}

// JobPath gets the prefix to all artifacts in GCS in the job
func (src *gcsJobSource) jobPath() string {
	return fmt.Sprintf("%s/%s", src.bucket, src.jobPrefix)
}
<commit_msg>tweak sleep times<commit_after>/*
Copyright 2018 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 spyglass

import (
	"context"
	"errors"
	"fmt"
	"io"
	"math/rand"
	"net/url"
	"path"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"github.com/sirupsen/logrus"
	"google.golang.org/api/iterator"

	"k8s.io/test-infra/prow/spyglass/viewers"
	"k8s.io/test-infra/testgrid/util/gcs"
)

const (
	httpScheme  = "http"
	httpsScheme = "https"
)

var (
	// ErrCannotParseSource is returned by newGCSJobSource when an incorrectly formatted source string is passed
	ErrCannotParseSource = errors.New("could not create job source from provided source")
)

// GCSArtifactFetcher contains information used for fetching artifacts from GCS
type GCSArtifactFetcher struct {
	client *storage.Client
}

// gcsJobSource is a location in GCS where Prow job-specific artifacts are stored. This implementation assumes
// Prow's native GCS upload format (treating GCS keys as a directory structure), and is not
// intended to support arbitrary GCS bucket upload formats.
type gcsJobSource struct {
	source     string
	linkPrefix string
	bucket     string
	jobPrefix  string
	jobName    string
	buildID    string
}

// NewGCSArtifactFetcher creates a new ArtifactFetcher with a real GCS Client
func NewGCSArtifactFetcher(c *storage.Client) *GCSArtifactFetcher {
	return &GCSArtifactFetcher{
		client: c,
	}
}

func fieldsForJob(src *gcsJobSource) logrus.Fields {
	return logrus.Fields{
		"jobPrefix": src.jobPath(),
	}
}

// newGCSJobSource creates a new gcsJobSource from a given bucket and jobPrefix
func newGCSJobSource(src string) (*gcsJobSource, error) {
	gcsURL, err := url.Parse(fmt.Sprintf("gs://%s", src))
	if err != nil {
		return &gcsJobSource{}, ErrCannotParseSource
	}
	gcsPath := &gcs.Path{}
	err = gcsPath.SetURL(gcsURL)
	if err != nil {
		return &gcsJobSource{}, ErrCannotParseSource
	}

	tokens := strings.FieldsFunc(gcsPath.Object(), func(c rune) bool { return c == '/' })
	if len(tokens) < 2 {
		return &gcsJobSource{}, ErrCannotParseSource
	}
	buildID := tokens[len(tokens)-1]
	name := tokens[len(tokens)-2]
	return &gcsJobSource{
		source:     src,
		linkPrefix: "gs://",
		bucket:     gcsPath.Bucket(),
		jobPrefix:  path.Clean(gcsPath.Object()) + "/",
		jobName:    name,
		buildID:    buildID,
	}, nil
}

// Artifacts lists all artifacts available for the given job source
func (af *GCSArtifactFetcher) artifacts(key string) ([]string, error) {
	src, err := newGCSJobSource(key)
	if err != nil {
		return nil, fmt.Errorf("Failed to get GCS job source from %s: %v", key, err)
	}

	listStart := time.Now()
	bucketName, prefix := extractBucketPrefixPair(src.jobPath())
	artifacts := []string{}
	bkt := af.client.Bucket(bucketName)
	q := storage.Query{
		Prefix:   prefix,
		Versions: false,
	}
	objIter := bkt.Objects(context.Background(), &q)
	wait := []time.Duration{16, 32, 64, 128, 256, 256, 512, 512}
	for i := 0; ; {
		oAttrs, err := objIter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			logrus.WithFields(fieldsForJob(src)).WithError(err).Error("Error accessing GCS artifact.")
			if i >= len(wait) {
				return artifacts, fmt.Errorf("timed out: error accessing GCS artifact: %v", err)
			}
			time.Sleep((wait[i] + time.Duration(rand.Intn(10))) * time.Millisecond)
			i++
			continue
		}
		artifacts = append(artifacts, strings.TrimPrefix(oAttrs.Name, prefix))
		i = 0
	}
	listElapsed := time.Since(listStart)
	logrus.WithField("duration", listElapsed).Infof("Listed %d artifacts.", len(artifacts))
	return artifacts, nil
}

type gcsArtifactHandle struct {
	*storage.ObjectHandle
}

func (h *gcsArtifactHandle) NewReader(ctx context.Context) (io.ReadCloser, error) {
	return h.ObjectHandle.NewReader(ctx)
}

func (h *gcsArtifactHandle) NewRangeReader(ctx context.Context, offset, length int64) (io.ReadCloser, error) {
	return h.ObjectHandle.NewRangeReader(ctx, offset, length)
}

// Artifact contructs a GCS artifact from the given GCS bucket and key. Uses the golang GCS library
// to get read handles. If the artifactName is not a valid key in the bucket a handle will still be
// constructed and returned, but all read operations will fail (dictated by behavior of golang GCS lib).
func (af *GCSArtifactFetcher) artifact(key string, artifactName string, sizeLimit int64) (viewers.Artifact, error) {
	src, err := newGCSJobSource(key)
	if err != nil {
		return nil, fmt.Errorf("Failed to get GCS job source from %s: %v", key, err)
	}

	bucketName, prefix := extractBucketPrefixPair(src.jobPath())
	bkt := af.client.Bucket(bucketName)
	obj := &gcsArtifactHandle{bkt.Object(path.Join(prefix, artifactName))}
	artifactLink := &url.URL{
		Scheme: httpsScheme,
		Host:   "storage.googleapis.com",
		Path:   path.Join(src.jobPath(), artifactName),
	}
	return NewGCSArtifact(context.Background(), obj, artifactLink.String(), artifactName, sizeLimit), nil
}

func extractBucketPrefixPair(gcsPath string) (string, string) {
	split := strings.SplitN(gcsPath, "/", 2)
	return split[0], split[1]
}

// CanonicalLink gets a link to the location of job-specific artifacts in GCS
func (src *gcsJobSource) canonicalLink() string {
	return path.Join(src.linkPrefix, src.bucket, src.jobPrefix)
}

// JobPath gets the prefix to all artifacts in GCS in the job
func (src *gcsJobSource) jobPath() string {
	return fmt.Sprintf("%s/%s", src.bucket, src.jobPrefix)
}
<|endoftext|>