<commit_before>// Copyright 2017 The etcd 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 etcdserver

import (
	"context"
	"time"

	"github.com/coreos/etcd/clientv3"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"github.com/coreos/etcd/mvcc"
	"github.com/coreos/etcd/pkg/types"
)

func (s *EtcdServer) monitorKVHash() {
	t := s.Cfg.CorruptCheckTime
	if t == 0 {
		return
	}
	plog.Infof("enabled corruption checking with %s interval", t)
	for {
		select {
		case <-s.stopping:
			return
		case <-time.After(t):
		}
		if !s.isLeader() {
			continue
		}
		if err := s.checkHashKV(); err != nil {
			plog.Debugf("check hash kv failed %v", err)
		}
	}
}

func (s *EtcdServer) checkHashKV() error {
	h, rev, crev, err := s.kv.HashByRev(0)
	if err != nil {
		plog.Fatalf("failed to hash kv store (%v)", err)
	}
	resps := s.getPeerHashKVs(rev)

	ctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
	err = s.linearizableReadNotify(ctx)
	cancel()
	if err != nil {
		return err
	}

	h2, rev2, crev2, err := s.kv.HashByRev(0)
	if err != nil {
		plog.Warningf("failed to hash kv store (%v)", err)
		return err
	}

	alarmed := false
	mismatch := func(id uint64) {
		if alarmed {
			return
		}
		alarmed = true
		a := &pb.AlarmRequest{
			MemberID: uint64(id),
			Action:   pb.AlarmRequest_ACTIVATE,
			Alarm:    pb.AlarmType_CORRUPT,
		}
		s.goAttach(func() {
			s.raftRequest(s.ctx, pb.InternalRaftRequest{Alarm: a})
		})
	}

	if h2 != h && rev2 == rev && crev == crev2 {
		plog.Warningf("mismatched hashes %d and %d for revision %d", h, h2, rev)
		mismatch(uint64(s.ID()))
	}

	for _, resp := range resps {
		id := resp.Header.MemberId

		// leader expects follower's latest revision less than or equal to leader's
		if resp.Header.Revision > rev2 {
			plog.Warningf(
				"revision %d from member %v, expected at most %d",
				resp.Header.Revision,
				types.ID(id),
				rev2)
			mismatch(id)
		}

		// leader expects follower's latest compact revision less than or equal to leader's
		if resp.CompactRevision > crev2 {
			plog.Warningf(
				"compact revision %d from member %v, expected at most %d",
				resp.CompactRevision,
				types.ID(id),
				crev2,
			)
			mismatch(id)
		}

		// follower's compact revision is leader's old one, then hashes must match
		if resp.CompactRevision == crev && resp.Hash != h {
			plog.Warningf(
				"hash %d at revision %d from member %v, expected hash %d",
				resp.Hash,
				rev,
				types.ID(id),
				h,
			)
			mismatch(id)
		}
	}
	return nil
}

func (s *EtcdServer) getPeerHashKVs(rev int64) (resps []*clientv3.HashKVResponse) {
	for _, m := range s.cluster.Members() {
		if m.ID == s.ID() {
			continue
		}

		cli, cerr := clientv3.New(clientv3.Config{
			DialTimeout: s.Cfg.ReqTimeout(),
			Endpoints:   m.PeerURLs,
		})
		if cerr != nil {
			plog.Warningf("%s failed to create client to peer %s for hash checking (%q)", s.ID(), types.ID(m.ID), cerr.Error())
			continue
		}

		respsLen := len(resps)
		for _, c := range cli.Endpoints() {
			ctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
			resp, herr := cli.HashKV(ctx, c, rev)
			cancel()
			if herr == nil {
				cerr = herr
				resps = append(resps, resp)
				break
			}
		}
		cli.Close()

		if respsLen == len(resps) {
			plog.Warningf("%s failed to hash kv for peer %s (%v)", s.ID(), types.ID(m.ID), cerr)
		}
	}
	return resps
}

type applierV3Corrupt struct {
	applierV3
}

func newApplierV3Corrupt(a applierV3) *applierV3Corrupt { return &applierV3Corrupt{a} }

func (a *applierV3Corrupt) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) Range(txn mvcc.TxnRead, p *pb.RangeRequest) (*pb.RangeResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) DeleteRange(txn mvcc.TxnWrite, p *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error) {
	return nil, nil, ErrCorrupt
}

func (a *applierV3Corrupt) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
	return nil, ErrCorrupt
}
<commit_msg>etcdserver: CheckInitialHashKV when "InitialCorruptCheck==true"<commit_after>// Copyright 2017 The etcd 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 etcdserver

import (
	"context"
	"fmt"
	"time"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
	"github.com/coreos/etcd/mvcc"
	"github.com/coreos/etcd/pkg/types"
)

// CheckInitialHashKV compares initial hash values with its peers
// before serving any peer/client traffic. Only mismatch when hashes
// are different at requested revision, with same compact revision.
func (s *EtcdServer) CheckInitialHashKV() error {
	if !s.Cfg.InitialCorruptCheck {
		return nil
	}

	plog.Infof("%s starting initial corruption check with timeout %v...", s.ID(), s.Cfg.ReqTimeout())
	h, rev, crev, err := s.kv.HashByRev(0)
	if err != nil {
		return fmt.Errorf("%s failed to fetch hash (%v)", s.ID(), err)
	}
	peers := s.getPeerHashKVs(rev)
	mismatch := 0
	for _, p := range peers {
		if p.resp != nil {
			peerID := types.ID(p.resp.Header.MemberId)
			if h != p.resp.Hash {
				if crev == p.resp.CompactRevision {
					plog.Errorf("%s's hash %d != %s's hash %d (revision %d, peer revision %d, compact revision %d)", s.ID(), h, peerID, p.resp.Hash, rev, p.resp.Header.Revision, crev)
					mismatch++
				} else {
					plog.Warningf("%s cannot check hash of peer(%s): peer has a different compact revision %d (revision:%d)", s.ID(), peerID, p.resp.CompactRevision, rev)
				}
			}
			continue
		}
		if p.err != nil {
			switch p.err {
			case rpctypes.ErrFutureRev:
				plog.Warningf("%s cannot check the hash of peer(%q) at revision %d: peer is lagging behind(%q)", s.ID(), p.eps, rev, p.err.Error())
			case rpctypes.ErrCompacted:
				plog.Warningf("%s cannot check the hash of peer(%q) at revision %d: local node is lagging behind(%q)", s.ID(), p.eps, rev, p.err.Error())
			}
		}
	}
	if mismatch > 0 {
		return fmt.Errorf("%s found data inconsistency with peers", s.ID())
	}

	plog.Infof("%s succeeded on initial corruption checking: no corruption", s.ID())
	return nil
}

func (s *EtcdServer) monitorKVHash() {
	t := s.Cfg.CorruptCheckTime
	if t == 0 {
		return
	}
	plog.Infof("enabled corruption checking with %s interval", t)
	for {
		select {
		case <-s.stopping:
			return
		case <-time.After(t):
		}
		if !s.isLeader() {
			continue
		}
		if err := s.checkHashKV(); err != nil {
			plog.Debugf("check hash kv failed %v", err)
		}
	}
}

func (s *EtcdServer) checkHashKV() error {
	h, rev, crev, err := s.kv.HashByRev(0)
	if err != nil {
		plog.Fatalf("failed to hash kv store (%v)", err)
	}
	peers := s.getPeerHashKVs(rev)

	ctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
	err = s.linearizableReadNotify(ctx)
	cancel()
	if err != nil {
		return err
	}

	h2, rev2, crev2, err := s.kv.HashByRev(0)
	if err != nil {
		plog.Warningf("failed to hash kv store (%v)", err)
		return err
	}

	alarmed := false
	mismatch := func(id uint64) {
		if alarmed {
			return
		}
		alarmed = true
		a := &pb.AlarmRequest{
			MemberID: uint64(id),
			Action:   pb.AlarmRequest_ACTIVATE,
			Alarm:    pb.AlarmType_CORRUPT,
		}
		s.goAttach(func() {
			s.raftRequest(s.ctx, pb.InternalRaftRequest{Alarm: a})
		})
	}

	if h2 != h && rev2 == rev && crev == crev2 {
		plog.Warningf("mismatched hashes %d and %d for revision %d", h, h2, rev)
		mismatch(uint64(s.ID()))
	}

	for _, p := range peers {
		if p.resp == nil {
			continue
		}
		id := p.resp.Header.MemberId

		// leader expects follower's latest revision less than or equal to leader's
		if p.resp.Header.Revision > rev2 {
			plog.Warningf(
				"revision %d from member %v, expected at most %d",
				p.resp.Header.Revision,
				types.ID(id),
				rev2)
			mismatch(id)
		}

		// leader expects follower's latest compact revision less than or equal to leader's
		if p.resp.CompactRevision > crev2 {
			plog.Warningf(
				"compact revision %d from member %v, expected at most %d",
				p.resp.CompactRevision,
				types.ID(id),
				crev2,
			)
			mismatch(id)
		}

		// follower's compact revision is leader's old one, then hashes must match
		if p.resp.CompactRevision == crev && p.resp.Hash != h {
			plog.Warningf(
				"hash %d at revision %d from member %v, expected hash %d",
				p.resp.Hash,
				rev,
				types.ID(id),
				h,
			)
			mismatch(id)
		}
	}
	return nil
}

type peerHashKVResp struct {
	resp *clientv3.HashKVResponse
	err  error
	eps  []string
}

func (s *EtcdServer) getPeerHashKVs(rev int64) (resps []*peerHashKVResp) {
	// TODO: handle the case when "s.cluster.Members" have not
	// been populated (e.g. no snapshot to load from disk)
	mbs := s.cluster.Members()
	pURLs := make([][]string, len(mbs))
	for _, m := range mbs {
		if m.ID == s.ID() {
			continue
		}
		pURLs = append(pURLs, m.PeerURLs)
	}

	for _, purls := range pURLs {
		if len(purls) == 0 {
			continue
		}
		cli, cerr := clientv3.New(clientv3.Config{
			DialTimeout: s.Cfg.ReqTimeout(),
			Endpoints:   purls,
		})
		if cerr != nil {
			plog.Warningf("%s failed to create client to peer %q for hash checking (%q)", s.ID(), purls, cerr.Error())
			continue
		}

		respsLen := len(resps)
		for _, c := range cli.Endpoints() {
			ctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
			var resp *clientv3.HashKVResponse
			resp, cerr = cli.HashKV(ctx, c, rev)
			cancel()
			if cerr == nil {
				resps = append(resps, &peerHashKVResp{resp: resp})
				break
			}
			plog.Warningf("%s hash-kv error %q on peer %q with revision %d", s.ID(), cerr.Error(), c, rev)
		}
		cli.Close()

		if respsLen == len(resps) {
			resps = append(resps, &peerHashKVResp{err: cerr, eps: purls})
		}
	}
	return resps
}

type applierV3Corrupt struct {
	applierV3
}

func newApplierV3Corrupt(a applierV3) *applierV3Corrupt { return &applierV3Corrupt{a} }

func (a *applierV3Corrupt) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) Range(txn mvcc.TxnRead, p *pb.RangeRequest) (*pb.RangeResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) DeleteRange(txn mvcc.TxnWrite, p *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error) {
	return nil, nil, ErrCorrupt
}

func (a *applierV3Corrupt) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
	return nil, ErrCorrupt
}

func (a *applierV3Corrupt) LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
	return nil, ErrCorrupt
}
<|endoftext|>


<commit_before>package dll_caller

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

var (
	kernel32, _        = syscall.LoadLibrary("kernel32.dll")
	getModuleHandle, _ = syscall.GetProcAddress(kernel32, "GetModuleHandleW")
)

type Dll struct {
	FileName   string
	dllHandler syscall.Handle
	funcProcs  map[string]uintptr
}

type FuncCallResult struct {
	Ret1  uintptr
	Ret2  uintptr
	Errno syscall.Errno
}

func NewDll(fileName string) (dll *Dll, err error) {
	newDll := new(Dll)
	if newDll.funcProcs == nil {
		newDll.funcProcs = make(map[string]uintptr)
	}

	if err = newDll.LoadLibrary(fileName); err != nil {
		return
	}

	return newDll, nil
}

func (p *Dll) LoadLibrary(fileName string) error {
	if handler, e := syscall.LoadLibrary(fileName); e != nil {
		return e
	} else {
		p.dllHandler = handler
	}
	return nil
}

func (p *Dll) FreeLibrary() error {
	if p.IsDllLoaded() {
		p.dllHandler = 0
		return syscall.FreeLibrary(p.dllHandler)
	}
	return nil
}

func (p *Dll) IsDllLoaded() bool {
	if uintptr(p.dllHandler) == 0 {
		return false
	}
	return true
}

func (p *Dll) InitalFunctions(funcNames ...string) error {
	if funcNames == nil {
		return nil
	}

	if !p.IsDllLoaded() {
		return errors.New("dll should loaded befor inital functions")
	}

	if p.funcProcs == nil {
		p.funcProcs = make(map[string]uintptr)
	}

	for _, funcName := range funcNames {
		funcName = strings.TrimSpace(funcName)
		if funcName == "" {
			return errors.New("function name could not be empty")
		}
		if proc, e := syscall.GetProcAddress(p.dllHandler, funcName); e != nil {
			return e
		} else {
			p.funcProcs[funcName] = proc
		}
	}
	return nil
}

func (p *Dll) Call(funcName string, funcParams ...interface{}) (result FuncCallResult, err error) {
	var lenParam uintptr = uintptr(len(funcParams))

	if p.funcProcs == nil {
		err = errors.New("function address not initaled")
		return
	}

	var funcAddress uintptr
	if addr, exist := p.funcProcs[funcName]; !exist {
		err = errors.New("function address not exist")
		return
	} else {
		funcAddress = addr
	}

	var r1, r2 uintptr
	var errno syscall.Errno

	var a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr

	for parmIndex, param := range funcParams {
		var vPtr uintptr = 0
		if strV, ok := param.(string); ok {
			vPtr = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(strV)))
		} else if stringPtrV, ok := param.(*string); ok {
			vPtr = uintptr(unsafe.Pointer(syscall.StringBytePtr(*stringPtrV)))
		} else if uint8ptrV, ok := param.(*uint8); ok {
			vPtr = uintptr(unsafe.Pointer(uint8ptrV))
		} else if intV, ok := param.(int); ok {
			vPtr = uintptr(intV)
		} else if int32V, ok := param.(int32); ok {
			vPtr = uintptr(int32V)
		} else if int64V, ok := param.(int64); ok {
			vPtr = uintptr(int64V)
		} else if uintPtrV, ok := param.(uintptr); ok {
			vPtr = uintPtrV
		} else {
			err = fmt.Errorf("unsupport convert type %v to uintptr", reflect.TypeOf(param))
			return
		}

		switch parmIndex + 1 {
		case 1:
			a1 = vPtr
		case 2:
			a2 = vPtr
		case 3:
			a3 = vPtr
		case 4:
			a4 = vPtr
		case 5:
			a5 = vPtr
		case 6:
			a6 = vPtr
		case 7:
			a7 = vPtr
		case 8:
			a8 = vPtr
		case 9:
			a9 = vPtr
		case 10:
			a10 = vPtr
		case 11:
			a11 = vPtr
		case 12:
			a12 = vPtr
		case 13:
			a13 = vPtr
		case 14:
			a14 = vPtr
		case 15:
			a15 = vPtr
		}
	}

	r1, r2, errno = syscall.Syscall15(funcAddress, lenParam, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
	result.Ret1 = r1
	result.Ret2 = r2
	result.Errno = errno

	return
}
<commit_msg>improve type switch<commit_after>package dll_caller

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

var (
	kernel32, _ = syscall.LoadLibrary("kernel32.dll")
)

type Dll struct {
	FileName   string
	dllHandler syscall.Handle
	funcProcs  map[string]uintptr
}

type FuncCallResult struct {
	Ret1  uintptr
	Ret2  uintptr
	Errno syscall.Errno
}

func NewDll(fileName string) (dll *Dll, err error) {
	newDll := new(Dll)
	if newDll.funcProcs == nil {
		newDll.funcProcs = make(map[string]uintptr)
	}

	if err = newDll.LoadLibrary(fileName); err != nil {
		return
	}

	return newDll, nil
}

func (p *Dll) LoadLibrary(fileName string) error {
	if handler, e := syscall.LoadLibrary(fileName); e != nil {
		return e
	} else {
		p.dllHandler = handler
	}
	return nil
}

func (p *Dll) FreeLibrary() error {
	if p.IsDllLoaded() {
		p.dllHandler = 0
		return syscall.FreeLibrary(p.dllHandler)
	}
	return nil
}

func (p *Dll) IsDllLoaded() bool {
	if uintptr(p.dllHandler) == 0 {
		return false
	}
	return true
}

func (p *Dll) InitalFunctions(funcNames ...string) error {
	if funcNames == nil {
		return nil
	}

	if !p.IsDllLoaded() {
		return errors.New("dll should loaded befor inital functions")
	}

	if p.funcProcs == nil {
		p.funcProcs = make(map[string]uintptr)
	}

	for _, funcName := range funcNames {
		funcName = strings.TrimSpace(funcName)
		if funcName == "" {
			return errors.New("function name could not be empty")
		}
		if proc, e := syscall.GetProcAddress(p.dllHandler, funcName); e != nil {
			return e
		} else {
			p.funcProcs[funcName] = proc
		}
	}
	return nil
}

func (p *Dll) Call(funcName string, funcParams ...interface{}) (result FuncCallResult, err error) {
	var lenParam uintptr = uintptr(len(funcParams))

	if p.funcProcs == nil {
		err = errors.New("function address not initaled")
		return
	}

	var funcAddress uintptr
	if addr, exist := p.funcProcs[funcName]; !exist {
		err = errors.New("function address not exist")
		return
	} else {
		funcAddress = addr
	}

	var r1, r2 uintptr
	var errno syscall.Errno

	var a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr

	for parmIndex, param := range funcParams {
		var vPtr uintptr = 0

		switch v := param.(type) {
		case string:
			vPtr = uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v)))
		case *string:
			vPtr = uintptr(unsafe.Pointer(syscall.StringBytePtr(*v)))
		case bool:
			vPtr = uintptr(unsafe.Pointer(&v))
		case int:
			vPtr = uintptr(v)
		case int8:
			vPtr = uintptr(v)
		case uint8:
			vPtr = uintptr(v)
		case *uint8:
			vPtr = uintptr(unsafe.Pointer(v))
		case int16:
			vPtr = uintptr(v)
		case uint16:
			vPtr = uintptr(v)
		case *uint16:
			vPtr = uintptr(unsafe.Pointer(v))
		case int32:
			vPtr = uintptr(v)
		case uint32:
			vPtr = uintptr(v)
		case *uint32:
			vPtr = uintptr(unsafe.Pointer(v))
		case int64:
			vPtr = uintptr(v)
		case uint64:
			vPtr = uintptr(v)
		case *uint64:
			vPtr = uintptr(unsafe.Pointer(v))
		case float32:
			vPtr = uintptr(v)
		case float64:
			vPtr = uintptr(v)
		case []byte:
			vPtr = uintptr(unsafe.Pointer(&v[0]))
		case uintptr:
			ptr, _ := param.(uintptr)
			vPtr = ptr
		default:
			err = fmt.Errorf("unsupport convert type %v to uintptr", reflect.TypeOf(param))
			return
		}

		switch parmIndex + 1 {
		case 1:
			a1 = vPtr
		case 2:
			a2 = vPtr
		case 3:
			a3 = vPtr
		case 4:
			a4 = vPtr
		case 5:
			a5 = vPtr
		case 6:
			a6 = vPtr
		case 7:
			a7 = vPtr
		case 8:
			a8 = vPtr
		case 9:
			a9 = vPtr
		case 10:
			a10 = vPtr
		case 11:
			a11 = vPtr
		case 12:
			a12 = vPtr
		case 13:
			a13 = vPtr
		case 14:
			a14 = vPtr
		case 15:
			a15 = vPtr
		}
	}

	r1, r2, errno = syscall.Syscall15(funcAddress, lenParam, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
	result.Ret1 = r1
	result.Ret2 = r2
	result.Errno = errno

	return
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	"html/template"
	"log"
	"math"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/go-systemd/daemon"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"github.com/hatstand/shinywaffle/calendar"
	"github.com/hatstand/shinywaffle/control"
	"github.com/hatstand/shinywaffle/telemetry"
	"github.com/hatstand/shinywaffle/weather"
	"google.golang.org/grpc"
)

var config = flag.String("config", "config.textproto", "Path to config proto")
var dryRun = flag.Bool("n", false, "Disables radiator commands")
var port = flag.Int("port", 8081, "Status port")
var grpcPort = flag.Int("grpc", 8082, "GRPC service port")

var (
	statusHtml = template.Must(template.New("status.html").Funcs(template.FuncMap{
		"convertColour": convertColour,
	}).ParseFiles("status.html", "weather.html"))
)

// convertColour converts a temperature in degrees Celsius into a hue value in the HSV space.
func convertColour(temp float64) int {
	clamped := math.Min(30, math.Max(0, temp)) * 4
	return int(240 + clamped)
}

type Interval struct {
	Width  int // Percentage from 0-100 of 24 hours
	Offset int // Percentage from 0-100 of 24 hours
}

type stubRadiatorController struct {
}

func (*stubRadiatorController) TurnOn(addr []byte) {
	log.Printf("Turning on radiator: %v\n", addr)
}

func (*stubRadiatorController) TurnOff(addr []byte) {
	log.Printf("Turning off radiator: %v\n", addr)
}

func createRadiatorController() control.RadiatorController {
	if *dryRun {
		return &stubRadiatorController{}
	} else {
		return control.NewRadioController()
	}
}

type ServeMux struct {
	api http.Handler
	ui  http.Handler
}

func NewServeMux(api http.Handler, ui http.Handler) *ServeMux {
	return &ServeMux{
		api: api,
		ui:  ui,
	}
}

func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if strings.HasPrefix(r.URL.Path, "/v1") {
		s.api.ServeHTTP(w, r)
	} else {
		s.ui.ServeHTTP(w, r)
	}
}

func main() {
	flag.Parse()

	fmt.Println("Hello, World!")
	fmt.Fprintf(os.Stderr, "Hello, World! (stderr)\n")

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	telemetry := telemetry.NewPublisher()
	err := telemetry.Hello()
	if err != nil {
		log.Fatalf("Failed to say hello to IoT: %v", err)
	}

	calendarService, err := calendar.NewCalendarScheduleService()
	if err != nil {
		log.Fatalf("Failed to start calendar service: %v", err)
	}

	controller := control.NewController(*config, createRadiatorController(), calendarService, telemetry)
	go controller.ControlRadiators(ctx)

	s := grpc.NewServer()
	control.RegisterHeatingControlServiceServer(s, controller)

	l, err := net.Listen("tcp", ":"+strconv.Itoa(*grpcPort))
	if err != nil {
		log.Fatalf("Failed to listen on GRPC port: %v", err)
	}
	go s.Serve(l)

	apiMux := runtime.NewServeMux()
	opts := []grpc.DialOption{grpc.WithInsecure()}
	err = control.RegisterHeatingControlServiceHandlerFromEndpoint(ctx, apiMux, ":8081", opts)
	if err != nil {
		log.Fatalf("Error starting GRPC gateway: %v", err)
	}

	uiMux := http.NewServeMux()
	uiMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, world!")
	})
	uiMux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "OK")
	})
	uiMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
		var ret []*control.GetZoneStatusReply
		zones, err := controller.GetZones(ctx, &control.GetZonesRequest{})
		if err == nil {
			for _, z := range zones.Zone {
				status, err := controller.GetZoneStatus(ctx, &control.GetZoneStatusRequest{
					Name: z.GetName(),
				})
				if err == nil {
					ret = append(ret, status)
				}
			}
		}
		weath, err := weather.FetchCurrentWeather("London")
		if err != nil {
			log.Printf("Failed to fetch current weather: %v", err)
			weath = nil
		}
		data := struct {
			Title   string
			Now     time.Time
			Zones   []*control.GetZoneStatusReply
			Error   error
			Weather *weather.Observation
		}{
			"foobar",
			time.Now(),
			ret,
			err,
			weath,
		}
		err = statusHtml.Execute(w, data)
		if err != nil {
			panic(err)
		}
	})

	srv := &http.Server{
		Addr:    ":" + strconv.Itoa(*port),
		Handler: NewServeMux(apiMux, uiMux),
	}
	go func() {
		log.Println("Listening...")
		ln, err := net.Listen("tcp", ":"+strconv.Itoa(*port))
		if err != nil {
			log.Fatalf("Failed to listen on port: %v", *port)
		}
		go func() {
			// Tells systemd that requests can now be served.
			daemon.SdNotify(false, daemon.SdNotifyReady)
			for {
				// Watchdog check.
				resp, err := http.Get("http://127.0.0.1:" + strconv.Itoa(*port))
				if err == nil {
					daemon.SdNotify(false, daemon.SdNotifyWatchdog)
				}
				resp.Body.Close()
				time.Sleep(5 * time.Second)
			}
		}()
		if err := srv.Serve(ln); err != nil {
			log.Println(err)
		}
	}()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)

	for {
		select {
		case <-ctx.Done():
			log.Println("Shutting down...")
			timeout, httpCancel := context.WithTimeout(ctx, 5*time.Second)
			defer httpCancel()
			srv.Shutdown(timeout)
			return
		case <-ch:
			cancel()
		}
	}
}
<commit_msg>Remove debug messages<commit_after>package main

import (
	"context"
	"flag"
	"fmt"
	"html/template"
	"log"
	"math"
	"net"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"time"

	"github.com/coreos/go-systemd/daemon"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"github.com/hatstand/shinywaffle/calendar"
	"github.com/hatstand/shinywaffle/control"
	"github.com/hatstand/shinywaffle/telemetry"
	"github.com/hatstand/shinywaffle/weather"
	"google.golang.org/grpc"
)

var config = flag.String("config", "config.textproto", "Path to config proto")
var dryRun = flag.Bool("n", false, "Disables radiator commands")
var port = flag.Int("port", 8081, "Status port")
var grpcPort = flag.Int("grpc", 8082, "GRPC service port")

var (
	statusHtml = template.Must(template.New("status.html").Funcs(template.FuncMap{
		"convertColour": convertColour,
	}).ParseFiles("status.html", "weather.html"))
)

// convertColour converts a temperature in degrees Celsius into a hue value in the HSV space.
func convertColour(temp float64) int {
	clamped := math.Min(30, math.Max(0, temp)) * 4
	return int(240 + clamped)
}

type Interval struct {
	Width  int // Percentage from 0-100 of 24 hours
	Offset int // Percentage from 0-100 of 24 hours
}

type stubRadiatorController struct {
}

func (*stubRadiatorController) TurnOn(addr []byte) {
	log.Printf("Turning on radiator: %v\n", addr)
}

func (*stubRadiatorController) TurnOff(addr []byte) {
	log.Printf("Turning off radiator: %v\n", addr)
}

func createRadiatorController() control.RadiatorController {
	if *dryRun {
		return &stubRadiatorController{}
	} else {
		return control.NewRadioController()
	}
}

type ServeMux struct {
	api http.Handler
	ui  http.Handler
}

func NewServeMux(api http.Handler, ui http.Handler) *ServeMux {
	return &ServeMux{
		api: api,
		ui:  ui,
	}
}

func (s *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if strings.HasPrefix(r.URL.Path, "/v1") {
		s.api.ServeHTTP(w, r)
	} else {
		s.ui.ServeHTTP(w, r)
	}
}

func main() {
	flag.Parse()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	telemetry := telemetry.NewPublisher()
	err := telemetry.Hello()
	if err != nil {
		log.Fatalf("Failed to say hello to IoT: %v", err)
	}

	calendarService, err := calendar.NewCalendarScheduleService()
	if err != nil {
		log.Fatalf("Failed to start calendar service: %v", err)
	}

	controller := control.NewController(*config, createRadiatorController(), calendarService, telemetry)
	go controller.ControlRadiators(ctx)

	s := grpc.NewServer()
	control.RegisterHeatingControlServiceServer(s, controller)

	l, err := net.Listen("tcp", ":"+strconv.Itoa(*grpcPort))
	if err != nil {
		log.Fatalf("Failed to listen on GRPC port: %v", err)
	}
	go s.Serve(l)

	apiMux := runtime.NewServeMux()
	opts := []grpc.DialOption{grpc.WithInsecure()}
	err = control.RegisterHeatingControlServiceHandlerFromEndpoint(ctx, apiMux, ":8081", opts)
	if err != nil {
		log.Fatalf("Error starting GRPC gateway: %v", err)
	}

	uiMux := http.NewServeMux()
	uiMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, world!")
	})
	uiMux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "OK")
	})
	uiMux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
		var ret []*control.GetZoneStatusReply
		zones, err := controller.GetZones(ctx, &control.GetZonesRequest{})
		if err == nil {
			for _, z := range zones.Zone {
				status, err := controller.GetZoneStatus(ctx, &control.GetZoneStatusRequest{
					Name: z.GetName(),
				})
				if err == nil {
					ret = append(ret, status)
				}
			}
		}
		weath, err := weather.FetchCurrentWeather("London")
		if err != nil {
			log.Printf("Failed to fetch current weather: %v", err)
			weath = nil
		}
		data := struct {
			Title   string
			Now     time.Time
			Zones   []*control.GetZoneStatusReply
			Error   error
			Weather *weather.Observation
		}{
			"foobar",
			time.Now(),
			ret,
			err,
			weath,
		}
		err = statusHtml.Execute(w, data)
		if err != nil {
			panic(err)
		}
	})

	srv := &http.Server{
		Addr:    ":" + strconv.Itoa(*port),
		Handler: NewServeMux(apiMux, uiMux),
	}
	go func() {
		log.Println("Listening...")
		ln, err := net.Listen("tcp", ":"+strconv.Itoa(*port))
		if err != nil {
			log.Fatalf("Failed to listen on port: %v", *port)
		}
		go func() {
			// Tells systemd that requests can now be served.
			daemon.SdNotify(false, daemon.SdNotifyReady)
			for {
				// Watchdog check.
				resp, err := http.Get("http://127.0.0.1:" + strconv.Itoa(*port))
				if err == nil {
					daemon.SdNotify(false, daemon.SdNotifyWatchdog)
				}
				resp.Body.Close()
				time.Sleep(5 * time.Second)
			}
		}()
		if err := srv.Serve(ln); err != nil {
			log.Println(err)
		}
	}()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)

	for {
		select {
		case <-ctx.Done():
			log.Println("Shutting down...")
			timeout, httpCancel := context.WithTimeout(ctx, 5*time.Second)
			defer httpCancel()
			srv.Shutdown(timeout)
			return
		case <-ch:
			cancel()
		}
	}
}
<|endoftext|>


<commit_before>package handler

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"

	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/cmd"
	"github.com/micro/go-micro/errors"
	"github.com/micro/micro/internal/helper"
)

type rpcRequest struct {
	Service string
	Method  string
	Address string
	Request interface{}
}

// RPC Handler passes on a JSON or form encoded RPC request to
// a service.
func RPC(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	badRequest := func(description string) {
		e := errors.BadRequest("go.micro.rpc", description)
		w.WriteHeader(400)
		w.Write([]byte(e.Error()))
	}

	var service, method, address string
	var request interface{}

	// response content type
	w.Header().Set("Content-Type", "application/json")

	ct := r.Header.Get("Content-Type")

	// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
	if idx := strings.IndexRune(ct, ';'); idx >= 0 {
		ct = ct[:idx]
	}

	switch ct {
	case "application/json":
		var rpcReq rpcRequest

		d := json.NewDecoder(r.Body)
		d.UseNumber()

		if err := d.Decode(&rpcReq); err != nil {
			badRequest(err.Error())
			return
		}

		service = rpcReq.Service
		method = rpcReq.Method
		address = rpcReq.Address
		request = rpcReq.Request

		// JSON as string
		if req, ok := rpcReq.Request.(string); ok {
			d := json.NewDecoder(strings.NewReader(req))
			d.UseNumber()

			if err := d.Decode(&request); err != nil {
				badRequest("error decoding request string: " + err.Error())
				return
			}
		}
	default:
		r.ParseForm()
		service = r.Form.Get("service")
		method = r.Form.Get("method")
		address = r.Form.Get("address")

		d := json.NewDecoder(strings.NewReader(r.Form.Get("request")))
		d.UseNumber()

		if err := d.Decode(&request); err != nil {
			badRequest("error decoding request string: " + err.Error())
			return
		}
	}

	if len(service) == 0 {
		badRequest("invalid service")
		return
	}

	if len(method) == 0 {
		badRequest("invalid method")
		return
	}

	// create request/response
	var response json.RawMessage
	var err error
	req := (*cmd.DefaultOptions().Client).NewRequest(service, method, request, client.WithContentType("application/json"))

	// create context
	ctx := helper.RequestToContext(r)

	// remote call
	if len(address) > 0 {
		err = (*cmd.DefaultOptions().Client).Call(ctx, req, &response, client.WithAddress(address))
	} else {
		err = (*cmd.DefaultOptions().Client).Call(ctx, req, &response)
	}
	if err != nil {
		ce := errors.Parse(err.Error())
		switch ce.Code {
		case 0:
			// assuming it's totally screwed
			ce.Code = 500
			ce.Id = "go.micro.rpc"
			ce.Status = http.StatusText(500)
			ce.Detail = "error during request: " + ce.Detail
			w.WriteHeader(500)
		default:
			w.WriteHeader(int(ce.Code))
		}
		w.Write([]byte(ce.Error()))
		return
	}

	b, _ := response.MarshalJSON()
	w.Header().Set("Content-Length", strconv.Itoa(len(b)))
	w.Write(b)
}
<commit_msg>Add: control timeout while serving HTTP for RPC.<commit_after>package handler

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"

	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/cmd"
	"github.com/micro/go-micro/errors"
	"github.com/micro/micro/internal/helper"
	"time"
)

type rpcRequest struct {
	Service string
	Method  string
	Address string
	Request interface{}
}

// RPC Handler passes on a JSON or form encoded RPC request to
// a service.
func RPC(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}
	defer r.Body.Close()

	badRequest := func(description string) {
		e := errors.BadRequest("go.micro.rpc", description)
		w.WriteHeader(400)
		w.Write([]byte(e.Error()))
	}

	var service, method, address string
	var request interface{}

	// response content type
	w.Header().Set("Content-Type", "application/json")

	ct := r.Header.Get("Content-Type")

	// Strip charset from Content-Type (like `application/json; charset=UTF-8`)
	if idx := strings.IndexRune(ct, ';'); idx >= 0 {
		ct = ct[:idx]
	}

	switch ct {
	case "application/json":
		var rpcReq rpcRequest

		d := json.NewDecoder(r.Body)
		d.UseNumber()

		if err := d.Decode(&rpcReq); err != nil {
			badRequest(err.Error())
			return
		}

		service = rpcReq.Service
		method = rpcReq.Method
		address = rpcReq.Address
		request = rpcReq.Request

		// JSON as string
		if req, ok := rpcReq.Request.(string); ok {
			d := json.NewDecoder(strings.NewReader(req))
			d.UseNumber()

			if err := d.Decode(&request); err != nil {
				badRequest("error decoding request string: " + err.Error())
				return
			}
		}
	default:
		r.ParseForm()
		service = r.Form.Get("service")
		method = r.Form.Get("method")
		address = r.Form.Get("address")

		d := json.NewDecoder(strings.NewReader(r.Form.Get("request")))
		d.UseNumber()

		if err := d.Decode(&request); err != nil {
			badRequest("error decoding request string: " + err.Error())
			return
		}
	}

	if len(service) == 0 {
		badRequest("invalid service")
		return
	}

	if len(method) == 0 {
		badRequest("invalid method")
		return
	}

	// create request/response
	var response json.RawMessage
	var err error
	req := (*cmd.DefaultOptions().Client).NewRequest(service, method, request, client.WithContentType("application/json"))

	// create context
	ctx := helper.RequestToContext(r)

	var opts []client.CallOption

	timeout, _ := strconv.Atoi(r.Header.Get("Timeout"))
	// set timeout
	if timeout > 0 {
		opts = append(opts, client.WithRequestTimeout(time.Duration(timeout)*time.Second))
	}

	// remote call
	if len(address) > 0 {
		opts = append(opts, client.WithAddress(address))
	}

	// remote call
	err = (*cmd.DefaultOptions().Client).Call(ctx, req, &response, opts...)
	if err != nil {
		ce := errors.Parse(err.Error())
		switch ce.Code {
		case 0:
			// assuming it's totally screwed
			ce.Code = 500
			ce.Id = "go.micro.rpc"
			ce.Status = http.StatusText(500)
			ce.Detail = "error during request: " + ce.Detail
			w.WriteHeader(500)
		default:
			w.WriteHeader(int(ce.Code))
		}
		w.Write([]byte(ce.Error()))
		return
	}

	b, _ := response.MarshalJSON()
	w.Header().Set("Content-Length", strconv.Itoa(len(b)))
	w.Write(b)
}
<|endoftext|>


<commit_before>package stats

import (
	"encoding/json"
	"fmt"
	"net/http"
	"runtime"
	"sync"
	"time"
)

type stats struct {
	mux *http.ServeMux

	sync.RWMutex

	Started int64  `json:"started"`
	Memory  string `json:"memory"`
	Threads int    `json:"threads"`
	GC      string `json:"gc_pause"`

	Counters []*counter `json:"counters"`

	running bool
	exit    chan bool
}

type counter struct {
	// time created
	Timestamp int64 `json:"timestamp"`
	// counters
	Status map[string]int `json:"status_codes"`
}

var (
	// 5 second window
	window = time.Second * 5
	// 120 seconds total
	total = 24
)

func (s *stats) handler(w http.ResponseWriter, r *http.Request) {
	s.RLock()
	b, err := json.Marshal(s)
	s.RUnlock()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(b)
}

func (s *stats) run() {
	t := time.NewTicker(window)
	w := 0

	for {
		select {
		case <-s.exit:
			t.Stop()
			return
		case <-t.C:
			// roll
			s.Lock()
			s.Counters = append(s.Counters, &counter{
				Timestamp: time.Now().Unix(),
				Status:    make(map[string]int),
			})
			if len(s.Counters) >= total {
				s.Counters = s.Counters[1:]
			}

			w++
			if w >= 2 {
				var mstat runtime.MemStats
				runtime.ReadMemStats(&mstat)
				s.Threads = runtime.NumGoroutine()
				s.Memory = fmt.Sprintf("%.2fmb", float64(mstat.Alloc)/float64(1024*1024))
				s.GC = fmt.Sprintf("%.3fms", float64(mstat.PauseTotalNs)/(1000*1000))
				w = 0
			}
			s.Unlock()
		}
	}
}

func (s *stats) Record(c string, t int) {
	s.Lock()
	counter := s.Counters[len(s.Counters)-1]
	if cnt, ok := counter.Status[c]; ok {
		cnt++
		counter.Status[c] = cnt
	} else {
		counter.Status[c] += t
	}
	s.Counters[len(s.Counters)-1] = counter
	s.Unlock()
}

func (s *stats) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var code string
	rw := &writer{w, 200}
	s.mux.ServeHTTP(rw, r)

	switch {
	case rw.status >= 500:
		code = "50x"
	case rw.status >= 400:
		code = "40x"
	case rw.status >= 300:
		code = "30x"
	case rw.status >= 200:
		code = "20x"
	}

	s.Record(code, 1)
}

func (s *stats) Start() error {
	s.Lock()
	defer s.Unlock()

	if s.running {
		return nil
	}

	s.Started = time.Now().Unix()
	s.exit = make(chan bool)
	go s.run()
	return nil
}

func (s *stats) Stop() error {
	s.Lock()
	defer s.Unlock()

	if !s.running {
		return nil
	}

	close(s.exit)
	s.Started = 0
	return nil
}

func New(p string, m *http.ServeMux) *stats {
	var mstat runtime.MemStats
	runtime.ReadMemStats(&mstat)

	s := &stats{
		mux:     m,
		Threads: runtime.NumGoroutine(),
		Memory:  fmt.Sprintf("%.2fmb", float64(mstat.Alloc)/float64(1024*1024)),
		GC:      fmt.Sprintf("%.3fms", float64(mstat.PauseTotalNs)/(1000*1000)),
		Counters: []*counter{
			&counter{
				Timestamp: time.Now().Unix(),
				Status:    make(map[string]int),
			},
		},
	}

	m.HandleFunc(p, s.handler)
	return s
}
<commit_msg>add total reqs<commit_after>package stats

import (
	"encoding/json"
	"fmt"
	"net/http"
	"runtime"
	"sync"
	"time"
)

type stats struct {
	mux *http.ServeMux

	sync.RWMutex

	Started int64  `json:"started"`
	Memory  string `json:"memory"`
	Threads int    `json:"threads"`
	GC      string `json:"gc_pause"`

	Counters []*counter `json:"counters"`

	running bool
	exit    chan bool
}

type counter struct {
	// time created
	Timestamp int64 `json:"timestamp"`
	// counters
	Status map[string]int `json:"status_codes"`
	Total  int            `json:"total_reqs"`
}

var (
	// 5 second window
	window = time.Second * 5
	// 120 seconds total
	total = 24
)

func (s *stats) handler(w http.ResponseWriter, r *http.Request) {
	s.RLock()
	b, err := json.Marshal(s)
	s.RUnlock()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(b)
}

func (s *stats) run() {
	t := time.NewTicker(window)
	w := 0

	for {
		select {
		case <-s.exit:
			t.Stop()
			return
		case <-t.C:
			// roll
			s.Lock()
			s.Counters = append(s.Counters, &counter{
				Timestamp: time.Now().Unix(),
				Status:    make(map[string]int),
			})
			if len(s.Counters) >= total {
				s.Counters = s.Counters[1:]
			}

			w++
			if w >= 2 {
				var mstat runtime.MemStats
				runtime.ReadMemStats(&mstat)
				s.Threads = runtime.NumGoroutine()
				s.Memory = fmt.Sprintf("%.2fmb", float64(mstat.Alloc)/float64(1024*1024))
				s.GC = fmt.Sprintf("%.3fms", float64(mstat.PauseTotalNs)/(1000*1000))
				w = 0
			}
			s.Unlock()
		}
	}
}

func (s *stats) Record(c string, t int) {
	s.Lock()
	counter := s.Counters[len(s.Counters)-1]
	counter.Status[c] += t
	counter.Total += t
	s.Counters[len(s.Counters)-1] = counter
	s.Unlock()
}

func (s *stats) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var code string
	rw := &writer{w, 200}
	s.mux.ServeHTTP(rw, r)

	switch {
	case rw.status >= 500:
		code = "50x"
	case rw.status >= 400:
		code = "40x"
	case rw.status >= 300:
		code = "30x"
	case rw.status >= 200:
		code = "20x"
	}

	s.Record(code, 1)
}

func (s *stats) Start() error {
	s.Lock()
	defer s.Unlock()

	if s.running {
		return nil
	}

	s.Started = time.Now().Unix()
	s.exit = make(chan bool)
	go s.run()
	return nil
}

func (s *stats) Stop() error {
	s.Lock()
	defer s.Unlock()

	if !s.running {
		return nil
	}

	close(s.exit)
	s.Started = 0
	return nil
}

func New(p string, m *http.ServeMux) *stats {
	var mstat runtime.MemStats
	runtime.ReadMemStats(&mstat)

	s := &stats{
		mux:     m,
		Threads: runtime.NumGoroutine(),
		Memory:  fmt.Sprintf("%.2fmb", float64(mstat.Alloc)/float64(1024*1024)),
		GC:      fmt.Sprintf("%.3fms", float64(mstat.PauseTotalNs)/(1000*1000)),
		Counters: []*counter{
			&counter{
				Timestamp: time.Now().Unix(),
				Status:    make(map[string]int),
			},
		},
	}

	m.HandleFunc(p, s.handler)
	return s
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/metadata"
)

const (
	//DefaultServerAddress amplifier address + port default
	DefaultServerAddress = "127.0.0.1:8080"
	//DefaultAdminServerAddress adm-server address + port default
	DefaultAdminServerAddress = "127.0.0.1:31315"
)

var (
	// amp is a singleton
	amp *AMP
)

func init() {
	grpclog.SetLogger(logger{})
}

// Logger is a simple log interface that also implements grpclog.Logger
type Logger interface {
	grpclog.Logger
	Panic(v ...interface{})
	Panicf(format string, v ...interface{})
	Panicln(v ...interface{})
}

// logger implements grpclog.Logger
type logger struct{}

func (l logger) Fatal(args ...interface{}) {
	if amp != nil {
		amp.Log.Fatal(args...)
	}
}
func (l logger) Fatalf(format string, args ...interface{}) {
	if amp != nil {
		amp.Log.Fatalf(format, args...)
	}
}
func (l logger) Fatalln(args ...interface{}) {
	if amp != nil {
		amp.Log.Fatalln(args...)
	}
}
func (l logger) Print(args ...interface{}) {
	if amp != nil {
		amp.Log.Print(args...)
	}
}
func (l logger) Printf(format string, args ...interface{}) {
	if amp != nil {
		amp.Log.Printf(format, args...)
	}
}
func (l logger) Println(args ...interface{}) {
	if amp != nil {
		amp.Log.Println(args...)
	}
}

// Configuration is for all configurable client settings
type Configuration struct {
	Verbose            bool
	GitHub             string
	Target             string
	Port               string
	ServerAddress      string
	AdminServerAddress string
	CmdTheme           string
}

// AMP holds the state for the current environment
type AMP struct {
	// Config contains all the configuration settings that were loaded
	Configuration *Configuration

	// Conn is the gRPC connection to amplifier
	Conn *grpc.ClientConn

	// Log also implements the grpclog.Logger interface
	Log Logger
}

// Connect to amplifier
func (a *AMP) Connect() error {
	conn, err := grpc.Dial(a.Configuration.ServerAddress,
		grpc.WithInsecure(),
		grpc.WithBlock(),
		grpc.WithTimeout(time.Second))
	if err != nil {
		return fmt.Errorf("Error connecting to amplifier @ %s: %v", a.Configuration.ServerAddress, err)
	}
	a.Conn = conn
	return nil
}

// Disconnect from amplifier
func (a *AMP) Disconnect() {
	if a.Conn == nil {
		return
	}
	err := a.Conn.Close()
	if err != nil {
		a.Log.Panic(err)
	}
}

// GetAuthorizedContext returns an authorized context
func (a *AMP) GetAuthorizedContext() (ctx context.Context, err error) {
	// TODO: reenable
	// Disabled temporally
	// if a.Configuration.Github == "" {
	// 	return nil, fmt.Errorf("Requires login")
	// }
	md := metadata.Pairs("sessionkey", a.Configuration.GitHub)
	ctx = metadata.NewContext(context.Background(), md)
	return
}

// Verbose returns true if verbose flag is set
func (a *AMP) Verbose() bool {
	return a.Configuration.Verbose
}

// NewAMP creates an AMP singleton instance
// (will only be configured with the first call)
func NewAMP(c *Configuration, l Logger) *AMP {
	if amp == nil {
		amp = &AMP{Configuration: c, Log: l}
	}
	return amp
}
<commit_msg>fix amplifier address<commit_after>package client

import (
	"fmt"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/metadata"
)

const (
	//DefaultServerAddress amplifier address + port default
	DefaultServerAddress = "m1:50101"
	//DefaultAdminServerAddress adm-server address + port default
	DefaultAdminServerAddress = "127.0.0.1:31315"
)

var (
	// amp is a singleton
	amp *AMP
)

func init() {
	grpclog.SetLogger(logger{})
}

// Logger is a simple log interface that also implements grpclog.Logger
type Logger interface {
	grpclog.Logger
	Panic(v ...interface{})
	Panicf(format string, v ...interface{})
	Panicln(v ...interface{})
}

// logger implements grpclog.Logger
type logger struct{}

func (l logger) Fatal(args ...interface{}) {
	if amp != nil {
		amp.Log.Fatal(args...)
	}
}
func (l logger) Fatalf(format string, args ...interface{}) {
	if amp != nil {
		amp.Log.Fatalf(format, args...)
	}
}
func (l logger) Fatalln(args ...interface{}) {
	if amp != nil {
		amp.Log.Fatalln(args...)
	}
}
func (l logger) Print(args ...interface{}) {
	if amp != nil {
		amp.Log.Print(args...)
	}
}
func (l logger) Printf(format string, args ...interface{}) {
	if amp != nil {
		amp.Log.Printf(format, args...)
	}
}
func (l logger) Println(args ...interface{}) {
	if amp != nil {
		amp.Log.Println(args...)
	}
}

// Configuration is for all configurable client settings
type Configuration struct {
	Verbose            bool
	GitHub             string
	Target             string
	Port               string
	ServerAddress      string
	AdminServerAddress string
	CmdTheme           string
}

// AMP holds the state for the current environment
type AMP struct {
	// Config contains all the configuration settings that were loaded
	Configuration *Configuration

	// Conn is the gRPC connection to amplifier
	Conn *grpc.ClientConn

	// Log also implements the grpclog.Logger interface
	Log Logger
}

// Connect to amplifier
func (a *AMP) Connect() error {
	conn, err := grpc.Dial(a.Configuration.ServerAddress,
		grpc.WithInsecure(),
		grpc.WithBlock(),
		grpc.WithTimeout(time.Second))
	if err != nil {
		return fmt.Errorf("Error connecting to amplifier @ %s: %v", a.Configuration.ServerAddress, err)
	}
	a.Conn = conn
	return nil
}

// Disconnect from amplifier
func (a *AMP) Disconnect() {
	if a.Conn == nil {
		return
	}
	err := a.Conn.Close()
	if err != nil {
		a.Log.Panic(err)
	}
}

// GetAuthorizedContext returns an authorized context
func (a *AMP) GetAuthorizedContext() (ctx context.Context, err error) {
	// TODO: reenable
	// Disabled temporally
	// if a.Configuration.Github == "" {
	// 	return nil, fmt.Errorf("Requires login")
	// }
	md := metadata.Pairs("sessionkey", a.Configuration.GitHub)
	ctx = metadata.NewContext(context.Background(), md)
	return
}

// Verbose returns true if verbose flag is set
func (a *AMP) Verbose() bool {
	return a.Configuration.Verbose
}

// NewAMP creates an AMP singleton instance
// (will only be configured with the first call)
func NewAMP(c *Configuration, l Logger) *AMP {
	if amp == nil {
		amp = &AMP{Configuration: c, Log: l}
	}
	return amp
}
<|endoftext|>


<commit_before>// Copyright 2014 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 main

import (
	configconvert "./../../source/configconvert"
	hostsetup "./../../source/hostsetup"
	"fmt"
	// specs "github.com/opencontainers/specs" //newest version
	specs "./../../source/specs"
	"log"
)

func testRootReadonlyTrue() {

	var guestProgrammeFile string
	guestProgrammeFile = "root_readonly_true_guest"
	var outputFile string
	outputFile = "readonly_true_out"
	err := hostsetup.SetupEnv(guestProgrammeFile, outputFile)
	if err != nil {
		log.Fatalf("Specstest root readonly test: hostsetup.SetupEnv error, %v", err)
	}
	fmt.Println("Host enviroment setting up for runc is already!")

	var filePath string
	filePath = "./../../source/config.json"

	var linuxspec *specs.LinuxSpec
	linuxspec, err = configconvert.ConfigToLinuxSpec(filePath)
	if err != nil {
		log.Fatalf("Specstestroot readonly test: readconfig error, %v", err)
	}

	linuxspec.Spec.Root.Path = "./rootfs_rootconfig"
	linuxspec.Spec.Root.Readonly = true
	linuxspec.Spec.Process.Args[0] = "./root_readonly_true_guest"
	linuxspec.Spec.Process.Terminal = true
	linuxspec.Spec.Process.User.UID = 0
	linuxspec.Spec.Process.User.GID = 0
	linuxspec.Spec.Process.User.AdditionalGids = nil
	err = configconvert.LinuxSpecToConfig(filePath, linuxspec)
	//err = wirteConfig(filePath, linuxspec)
	if err != nil {
		log.Fatalf("Specstest root readonly test: writeconfig error, %v", err)
	}
	fmt.Println("Host enviroment for runc is already!")

}

func main() {
	testRootReadonlyTrue()
}
<commit_msg>Correct readonlyTrue testcase to fix terminal err.<commit_after>// Copyright 2014 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 main

import (
	configconvert "./../../source/configconvert"
	hostsetup "./../../source/hostsetup"
	"fmt"
	// specs "github.com/opencontainers/specs" //newest version
	specs "./../../source/specs"
	"log"
)

func testRootReadonlyTrue() {

	var guestProgrammeFile string
	guestProgrammeFile = "root_readonly_true_guest"
	var outputFile string
	outputFile = "readonly_true_out"
	err := hostsetup.SetupEnv(guestProgrammeFile, outputFile)
	if err != nil {
		log.Fatalf("Specstest root readonly test: hostsetup.SetupEnv error, %v", err)
	}
	fmt.Println("Host enviroment setting up for runc is already!")

	var filePath string
	filePath = "./../../source/config.json"

	var linuxspec *specs.LinuxSpec
	linuxspec, err = configconvert.ConfigToLinuxSpec(filePath)
	if err != nil {
		log.Fatalf("Specstestroot readonly test: readconfig error, %v", err)
	}

	linuxspec.Spec.Root.Path = "./rootfs_rootconfig"
	linuxspec.Spec.Root.Readonly = true
	linuxspec.Spec.Process.Args[0] = "./root_readonly_true_guest"
	linuxspec.Spec.Process.Terminal = false
	linuxspec.Spec.Process.User.UID = 0
	linuxspec.Spec.Process.User.GID = 0
	linuxspec.Spec.Process.User.AdditionalGids = nil
	err = configconvert.LinuxSpecToConfig(filePath, linuxspec)
	//err = wirteConfig(filePath, linuxspec)
	if err != nil {
		log.Fatalf("Specstest root readonly test: writeconfig error, %v", err)
	}
	fmt.Println("Host enviroment for runc is already!")

}

func main() {
	testRootReadonlyTrue()
}
<|endoftext|>


<commit_before>package pprint

var (
	comma  = NewText(",")
	dot    = NewText(".")
	lparen = NewText("(")
	rparen = NewText(")")
)

// NewCSV wraps `elements` with a comma separated list.
func NewCSV(elements ...Element) Element {
	if len(elements) == 0 {
		return Empty
	}
	elts := make([]Element, len(elements)*3-2)
	pos := 0
	for _, elt := range elements {
		if pos == 0 {
			elts[pos] = elt
			pos++
		} else {
			elts[pos] = comma
			elts[pos+1] = CondLB
			elts[pos+2] = elt
			pos += 3
		}
	}
	return NewNest(NewConcat(elts...))
}

// NewArgs formats `elements` in a manner suitable for C style
// arguments.
func NewArgs(elements ...Element) Element {
	return NewConcat(lparen, NewCSV(elements...), rparen)
}

// NewDottedList formats `elements` in a manner suitable for chained
// method calls, á la "fluent" interfaces.
func NewDottedList(elements ...Element) Element {
	if len(elements) == 0 {
		return Empty
	} else if len(elements) == 1 {
		return NewNest(elements[0])
	}
	elts := make([]Element, len(elements)*3-2)
	pos := 0
	for _, elt := range elements {
		if pos == 0 {
			elts[pos] = elt
			pos++
		} else if pos == 1 {
			// we don't want to break on the first dot; it's ugly.
			elts[pos] = dot
			elts[pos+1] = elt
			pos += 2
		} else {
			elts[pos] = DotLB
			elts[pos+1] = elt
			pos += 2
		}
	}
	// Bit involved; we want NewDottedList(a, b, c) to turn into
	// Concat(a, Nest(Concat(".", b, DotLB, c))).  We want this
	// because it means that the dots line up on linebreaks nicely.
	return NewConcat(elts[0], NewNest(NewConcat(elts[1:]...)))
}

// NewFuncall formats `args` as a function call of the function
// `name`.
func NewFuncall(name string, args ...Element) Element {
	return NewConcat(NewText(name), NewArgs(args...))
}
<commit_msg>Use correct array length<commit_after>package pprint

var (
	comma  = NewText(",")
	dot    = NewText(".")
	lparen = NewText("(")
	rparen = NewText(")")
)

// NewCSV wraps `elements` with a comma separated list.
func NewCSV(elements ...Element) Element {
	if len(elements) == 0 {
		return Empty
	}
	elts := make([]Element, len(elements)*3-2)
	pos := 0
	for _, elt := range elements {
		if pos == 0 {
			elts[pos] = elt
			pos++
		} else {
			elts[pos] = comma
			elts[pos+1] = CondLB
			elts[pos+2] = elt
			pos += 3
		}
	}
	return NewNest(NewConcat(elts...))
}

// NewArgs formats `elements` in a manner suitable for C style
// arguments.
func NewArgs(elements ...Element) Element {
	return NewConcat(lparen, NewCSV(elements...), rparen)
}

// NewDottedList formats `elements` in a manner suitable for chained
// method calls, á la "fluent" interfaces.
func NewDottedList(elements ...Element) Element {
	if len(elements) == 0 {
		return Empty
	} else if len(elements) == 1 {
		return NewNest(elements[0])
	}
	elts := make([]Element, len(elements)*2-1)
	pos := 0
	for _, elt := range elements {
		if pos == 0 {
			elts[pos] = elt
			pos++
		} else if pos == 1 {
			// we don't want to break on the first dot; it's ugly.
			elts[pos] = dot
			elts[pos+1] = elt
			pos += 2
		} else {
			elts[pos] = DotLB
			elts[pos+1] = elt
			pos += 2
		}
	}
	// Bit involved; we want NewDottedList(a, b, c) to turn into
	// Concat(a, Nest(Concat(".", b, DotLB, c))).  We want this
	// because it means that the dots line up on linebreaks nicely.
	return NewConcat(elts[0], NewNest(NewConcat(elts[1:]...)))
}

// NewFuncall formats `args` as a function call of the function
// `name`.
func NewFuncall(name string, args ...Element) Element {
	return NewConcat(NewText(name), NewArgs(args...))
}
<|endoftext|>


<commit_before>// Copyright 2014 The Cockroach 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.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package util

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"time"

	"golang.org/x/net/context"

	"github.com/cockroachdb/cockroach/util/caller"
	"github.com/cockroachdb/cockroach/util/log"
	"github.com/cockroachdb/cockroach/util/timeutil"
)

// Tester is a proxy for e.g. testing.T which does not introduce a dependency
// on "testing".
type Tester interface {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Failed() bool
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
}

type panicTesterImpl struct{}

// PanicTester is a Tester which panics.
var PanicTester = panicTesterImpl{}

func (panicTesterImpl) Failed() bool { return false }

func (pt panicTesterImpl) Error(args ...interface{}) {
	pt.Fatal(args...)
}

func (pt panicTesterImpl) Errorf(format string, args ...interface{}) {
	pt.Fatalf(format, args...)
}

func (panicTesterImpl) Fatal(args ...interface{}) {
	panic(fmt.Sprint(args...))
}

func (panicTesterImpl) Fatalf(format string, args ...interface{}) {
	panic(fmt.Sprintf(format, args...))
}

// CreateTempDir creates a temporary directory and returns its path.
// You should usually call defer CleanupDir(dir) right after.
func CreateTempDir(t Tester, prefix string) string {
	dir, err := ioutil.TempDir("", prefix)
	if err != nil {
		t.Fatal(err)
	}
	return dir
}

// CreateRestrictedFile creates a file on disk which contains the
// supplied byte string as its content. The resulting file will have restrictive
// permissions; specifically, u=rw (0600). Returns the path of the created file
// along with a function that will delete the created file.
//
// This is needed for some Go libraries (e.g. postgres SQL driver) which will
// refuse to open certificate files that have overly permissive permissions.
func CreateRestrictedFile(t Tester, contents []byte, tempdir, name string) string {
	tempPath := filepath.Join(tempdir, name)
	if err := ioutil.WriteFile(tempPath, contents, 0600); err != nil {
		if t == nil {
			log.Fatal(context.TODO(), err)
		} else {
			t.Fatal(err)
		}
	}
	return tempPath
}

// CleanupDir removes the passed-in directory and all contents. Errors are ignored.
func CleanupDir(dir string) {
	_ = os.RemoveAll(dir)
}

const defaultSucceedsSoonDuration = 15 * time.Second

// SucceedsSoon fails the test (with t.Fatal) unless the supplied
// function runs without error within a preset maximum duration. The
// function is invoked immediately at first and then successively with
// an exponential backoff starting at 1ns and ending at the maximum
// duration (currently 15s).
func SucceedsSoon(t Tester, fn func() error) {
	SucceedsSoonDepth(1, t, fn)
}

// SucceedsSoonDepth is like SucceedsSoon() but with an additional
// stack depth offset.
func SucceedsSoonDepth(depth int, t Tester, fn func() error) {
	if err := RetryForDuration(defaultSucceedsSoonDuration, fn); err != nil {
		file, line, _ := caller.Lookup(depth + 1)
		t.Fatalf("%s:%d, condition failed to evaluate within %s: %s", file, line, defaultSucceedsSoonDuration, err)
	}
}

// RetryForDuration will retry the given function until it either returns
// without error, or the given duration has elapsed. The function is invoked
// immediately at first and then successively with an exponential backoff
// starting at 1ns and ending at the specified duration.
func RetryForDuration(duration time.Duration, fn func() error) error {
	deadline := timeutil.Now().Add(duration)
	var lastErr error
	for wait := time.Duration(1); timeutil.Now().Before(deadline); wait *= 2 {
		lastErr = fn()
		if lastErr == nil {
			return nil
		}
		if wait > time.Second {
			wait = time.Second
		}
		time.Sleep(wait)
	}
	return lastErr
}

// NoZeroField returns nil if none of the fields of the struct underlying the
// interface are equal to the zero value, and an error otherwise.
// It will panic if the struct has unexported fields and for any non-struct.
func NoZeroField(v interface{}) error {
	ele := reflect.Indirect(reflect.ValueOf(v))
	eleT := ele.Type()
	for i := 0; i < ele.NumField(); i++ {
		f := ele.Field(i)
		zero := reflect.Zero(f.Type())
		if reflect.DeepEqual(f.Interface(), zero.Interface()) {
			return fmt.Errorf("expected %s field to be non-zero", eleT.Field(i).Name)
		}
	}
	return nil
}
<commit_msg>util: bump default succeeds soon timeout to 45s<commit_after>// Copyright 2014 The Cockroach 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.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package util

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"time"

	"golang.org/x/net/context"

	"github.com/cockroachdb/cockroach/util/caller"
	"github.com/cockroachdb/cockroach/util/log"
	"github.com/cockroachdb/cockroach/util/timeutil"
)

// Tester is a proxy for e.g. testing.T which does not introduce a dependency
// on "testing".
type Tester interface {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Failed() bool
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
}

type panicTesterImpl struct{}

// PanicTester is a Tester which panics.
var PanicTester = panicTesterImpl{}

func (panicTesterImpl) Failed() bool { return false }

func (pt panicTesterImpl) Error(args ...interface{}) {
	pt.Fatal(args...)
}

func (pt panicTesterImpl) Errorf(format string, args ...interface{}) {
	pt.Fatalf(format, args...)
}

func (panicTesterImpl) Fatal(args ...interface{}) {
	panic(fmt.Sprint(args...))
}

func (panicTesterImpl) Fatalf(format string, args ...interface{}) {
	panic(fmt.Sprintf(format, args...))
}

// CreateTempDir creates a temporary directory and returns its path.
// You should usually call defer CleanupDir(dir) right after.
func CreateTempDir(t Tester, prefix string) string {
	dir, err := ioutil.TempDir("", prefix)
	if err != nil {
		t.Fatal(err)
	}
	return dir
}

// CreateRestrictedFile creates a file on disk which contains the
// supplied byte string as its content. The resulting file will have restrictive
// permissions; specifically, u=rw (0600). Returns the path of the created file
// along with a function that will delete the created file.
//
// This is needed for some Go libraries (e.g. postgres SQL driver) which will
// refuse to open certificate files that have overly permissive permissions.
func CreateRestrictedFile(t Tester, contents []byte, tempdir, name string) string {
	tempPath := filepath.Join(tempdir, name)
	if err := ioutil.WriteFile(tempPath, contents, 0600); err != nil {
		if t == nil {
			log.Fatal(context.TODO(), err)
		} else {
			t.Fatal(err)
		}
	}
	return tempPath
}

// CleanupDir removes the passed-in directory and all contents. Errors are ignored.
func CleanupDir(dir string) {
	_ = os.RemoveAll(dir)
}

const defaultSucceedsSoonDuration = 45 * time.Second

// SucceedsSoon fails the test (with t.Fatal) unless the supplied
// function runs without error within a preset maximum duration. The
// function is invoked immediately at first and then successively with
// an exponential backoff starting at 1ns and ending at the maximum
// duration (currently 15s).
func SucceedsSoon(t Tester, fn func() error) {
	SucceedsSoonDepth(1, t, fn)
}

// SucceedsSoonDepth is like SucceedsSoon() but with an additional
// stack depth offset.
func SucceedsSoonDepth(depth int, t Tester, fn func() error) {
	if err := RetryForDuration(defaultSucceedsSoonDuration, fn); err != nil {
		file, line, _ := caller.Lookup(depth + 1)
		t.Fatalf("%s:%d, condition failed to evaluate within %s: %s", file, line, defaultSucceedsSoonDuration, err)
	}
}

// RetryForDuration will retry the given function until it either returns
// without error, or the given duration has elapsed. The function is invoked
// immediately at first and then successively with an exponential backoff
// starting at 1ns and ending at the specified duration.
func RetryForDuration(duration time.Duration, fn func() error) error {
	deadline := timeutil.Now().Add(duration)
	var lastErr error
	for wait := time.Duration(1); timeutil.Now().Before(deadline); wait *= 2 {
		lastErr = fn()
		if lastErr == nil {
			return nil
		}
		if wait > time.Second {
			wait = time.Second
		}
		time.Sleep(wait)
	}
	return lastErr
}

// NoZeroField returns nil if none of the fields of the struct underlying the
// interface are equal to the zero value, and an error otherwise.
// It will panic if the struct has unexported fields and for any non-struct.
func NoZeroField(v interface{}) error {
	ele := reflect.Indirect(reflect.ValueOf(v))
	eleT := ele.Type()
	for i := 0; i < ele.NumField(); i++ {
		f := ele.Field(i)
		zero := reflect.Zero(f.Type())
		if reflect.DeepEqual(f.Interface(), zero.Interface()) {
			return fmt.Errorf("expected %s field to be non-zero", eleT.Field(i).Name)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package util

// VERSION identifies Khan's current version
var VERSION = "3.3.5"
<commit_msg>Release 3.3.10<commit_after>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package util

// VERSION identifies Khan's current version
var VERSION = "3.3.10"
<|endoftext|>


<commit_before>package util

var Version string = "2.0-beta"
<commit_msg>Bump version<commit_after>package util

var Version string = "2.0-beta-2"
<|endoftext|>


<commit_before>package solidserver

import (
  "github.com/hashicorp/terraform/helper/schema"
  "encoding/json"
  "net/url"
  "strconv"
  "strings"
  "fmt"
  "log"
)

func resourcednsrr() *schema.Resource {
  return &schema.Resource{
    Create: resourcednsrrCreate,
    Read:   resourcednsrrRead,
    Update: resourcednsrrUpdate,
    Delete: resourcednsrrDelete,
    Importer: &schema.ResourceImporter{
        State: resourcednsrrImportState,
    },

    Schema: map[string]*schema.Schema{
      "dnsserver": &schema.Schema{
        Type:        schema.TypeString,
        Description: "The managed SMART DNS server name, or DNS server name hosting the RR's zone.",
        Required:    true,
        ForceNew:    true,
      },
      "name": &schema.Schema{
        Type:        schema.TypeString,
        Description: "The Fully Qualified Domain Name of the RR to create.",
        Required:    true,
        ForceNew:    true,
      },
      "type": &schema.Schema{
        Type:         schema.TypeString,
        Description:  "The type of the RR to create (Supported : A, AAAA, CNAME).",
        ValidateFunc: resourcednsrrvalidatetype,
        Required:     true,
        ForceNew:     true,
      },
      "value": &schema.Schema{
        Type:        schema.TypeString,
        Description: "The value od the RR to create.",
        Computed:    false,
        Required:    true,
      },
      "ttl": &schema.Schema{
        Type:        schema.TypeInt,
        Description: "The DNS Time To Live of the RR to create.",
        Optional:    true,
        Default:     3600,
      },
    },
  }
}

func resourcednsrrvalidatetype(v interface{}, _ string) ([]string, []error) {
  switch strings.ToUpper(v.(string)){
    case "A":
      return nil, nil
    case "AAAA":
      return nil, nil
    case "CNAME":
      return nil, nil
    default:
      return nil, []error{fmt.Errorf("Unsupported RR type.")}
  }
}

func resourcednsrrImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
  s := meta.(*SOLIDserver)

  results := make([]*schema.ResourceData, 1, 1)
	results[0] = d

  parameters := url.Values{}
  parameters.Add("rr_id", results[0].Id())

  // Sending the read request
  http_resp, body, _ := s.Request("get", "rest/dns_rr_info", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 200 && len(buf) > 0) {
    ttl, _ := strconv.Atoi(buf[0]["ttl"].(string))

    d.Set("dnsserver", buf[0]["dns_name"].(string))
    d.Set("name", buf[0]["rr_full_name"].(string))
    d.Set("type", buf[0]["rr_type"].(string))
    d.Set("value", buf[0]["value1"].(string))
    d.Set("ttl", ttl)

    return []*schema.ResourceData{d}, nil
  }

  if (len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      // Log the error
      log.Printf("[DEBUG] SOLIDServer - Unable to find RR: %s (%s)", d.Get("name"), errmsg)
    }
  } else {
    // Log the error
    log.Printf("[DEBUG] SOLIDServer - Unable to find RR (oid): %s", d.Id())
  }

  // Do not unset the local ID to avoid inconsistency

  // Reporting a failure
  return nil, fmt.Errorf("SOLIDServer - Unable to find RR: %s", d.Get("name").(string))

}

func resourcednsrrCreate(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("dns_name", d.Get("dnsserver").(string))
  parameters.Add("rr_name", d.Get("name").(string))
  parameters.Add("rr_type", strings.ToUpper(d.Get("type").(string)))
  parameters.Add("value1", d.Get("value").(string))
  parameters.Add("rr_ttl", strconv.Itoa(d.Get("ttl").(int)))

  // Sending the creation request
  http_resp, body, _ := s.Request("post", "rest/dns_rr_add", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 201 && len(buf) > 0) {
    if oid, oid_exist := buf[0]["ret_oid"].(string); (oid_exist) {
      log.Printf("[DEBUG] SOLIDServer - Created RR (oid): %s", oid)
      d.SetId(oid)
      return nil
    }
  }

  // Reporting a failure
  return fmt.Errorf("SOLIDServer - Unable to create RR record: %s", d.Get("name").(string))
}

func resourcednsrrUpdate(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("rr_id", d.Id())
  parameters.Add("dns_name", d.Get("dnsserver").(string))
  parameters.Add("rr_name", d.Get("name").(string))
  parameters.Add("rr_type", strings.ToUpper(d.Get("type").(string)))
  parameters.Add("value1", d.Get("value").(string))
  parameters.Add("rr_ttl", strconv.Itoa(d.Get("ttl").(int)))

  // Sending the update request
  http_resp, body, _ := s.Request("put", "rest/dns_rr_add", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 200 && len(buf) > 0) {
    if oid, oid_exist := buf[0]["ret_oid"].(string); (oid_exist) {
      log.Printf("[DEBUG] SOLIDServer - Updated RR (oid): %s", oid)
      d.SetId(oid)
      return nil
    }
  }

  // Reporting a failure
  return fmt.Errorf("SOLIDServer - Unable to update RR: %s", d.Get("name").(string))
}

func resourcednsrrDelete(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("rr_id", d.Id())

  // Sending the deletion request
  http_resp, body, _ := s.Request("delete", "rest/dns_rr_delete", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode != 204 && len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      log.Printf("[DEBUG] SOLIDServer - Unable to delete RR: %s (%s)", d.Get("name"), errmsg)
    }
  }

  // Log deletion
  log.Printf("[DEBUG] SOLIDServer - Deleted RR (oid): %s", d.Id())

  // Unset local ID
  d.SetId("")

  return nil
}

func resourcednsrrRead(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("rr_id", d.Id())

  // Sending the read request
  http_resp, body, _ := s.Request("get", "rest/dns_rr_info", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 200 && len(buf) > 0) {
    ttl, _ := strconv.Atoi(buf[0]["ttl"].(string))

    d.Set("dnsserver", buf[0]["dns_name"].(string))
    d.Set("name", buf[0]["rr_full_name"].(string))
    d.Set("type", buf[0]["rr_type"].(string))
    d.Set("value", buf[0]["value1"].(string))
    d.Set("ttl", ttl)

    return nil
  }

  if (len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      // Log the error
      log.Printf("[DEBUG] SOLIDServer - Unable to find RR: %s (%s)", d.Get("name"), errmsg)
    }
  } else {
    // Log the error
    log.Printf("[DEBUG] SOLIDServer - Unable to find RR (oid): %s", d.Id())
  }

  // Do not unset the local ID to avoid inconsistency

  // Reporting a failure
  return fmt.Errorf("SOLIDServer - Unable to find RR: %s", d.Get("name").(string))
}
<commit_msg>Implement Exists<commit_after>package solidserver

import (
  "github.com/hashicorp/terraform/helper/schema"
  "encoding/json"
  "net/url"
  "strconv"
  "strings"
  "fmt"
  "log"
)

func resourcednsrr() *schema.Resource {
  return &schema.Resource{
    Create: resourcednsrrCreate,
    Read:   resourcednsrrRead,
    Update: resourcednsrrUpdate,
    Delete: resourcednsrrDelete,
    Exists: resourcednsrrExists,
    Importer: &schema.ResourceImporter{
        State: resourcednsrrImportState,
    },

    Schema: map[string]*schema.Schema{
      "dnsserver": &schema.Schema{
        Type:        schema.TypeString,
        Description: "The managed SMART DNS server name, or DNS server name hosting the RR's zone.",
        Required:    true,
        ForceNew:    true,
      },
      "name": &schema.Schema{
        Type:        schema.TypeString,
        Description: "The Fully Qualified Domain Name of the RR to create.",
        Required:    true,
        ForceNew:    true,
      },
      "type": &schema.Schema{
        Type:         schema.TypeString,
        Description:  "The type of the RR to create (Supported : A, AAAA, CNAME).",
        ValidateFunc: resourcednsrrvalidatetype,
        Required:     true,
        ForceNew:     true,
      },
      "value": &schema.Schema{
        Type:        schema.TypeString,
        Description: "The value od the RR to create.",
        Computed:    false,
        Required:    true,
      },
      "ttl": &schema.Schema{
        Type:        schema.TypeInt,
        Description: "The DNS Time To Live of the RR to create.",
        Optional:    true,
        Default:     3600,
      },
    },
  }
}

func resourcednsrrvalidatetype(v interface{}, _ string) ([]string, []error) {
  switch strings.ToUpper(v.(string)){
    case "A":
      return nil, nil
    case "AAAA":
      return nil, nil
    case "CNAME":
      return nil, nil
    default:
      return nil, []error{fmt.Errorf("Unsupported RR type.")}
  }
}

func resourcednsrrExists(d *schema.ResourceData, meta interface{}) (bool, error) {
  s := meta.(*SOLIDserver)

  results := make([]*schema.ResourceData, 1, 1)
	results[0] = d

  parameters := url.Values{}
  parameters.Add("rr_id", results[0].Id())

  log.Printf("[INFO] Checking existence of Record Id: %s", results[0].Id())

  // Sending the read request
  http_resp, body, _ := s.Request("get", "rest/dns_rr_info", &parameters)
  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  if http_resp.StatusCode == 200 && len(buf) > 0 {
    return true, nil
  } else if http_resp.StatusCode == 204 {
    return false, nil
  }

  if (len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      // Log the error
      log.Printf("[DEBUG] SOLIDServer - Unable to find RR: %s (%s)", d.Get("name"), errmsg)
    }
  } else {
    // Log the error
    log.Printf("[DEBUG] SOLIDServer - Unable to find RR (oid): %s", d.Id())
  }

  return false, fmt.Errorf("SOLIDServer - Unable to find RR: %s", d.Get("name").(string))

}

func resourcednsrrImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
  s := meta.(*SOLIDserver)

  results := make([]*schema.ResourceData, 1, 1)
	results[0] = d

  parameters := url.Values{}
  parameters.Add("rr_id", results[0].Id())

  // Sending the read request
  http_resp, body, _ := s.Request("get", "rest/dns_rr_info", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer

  if (http_resp.StatusCode == 200 && len(buf) > 0) {
    ttl, _ := strconv.Atoi(buf[0]["ttl"].(string))

    d.Set("dnsserver", buf[0]["dns_name"].(string))
    d.Set("name", buf[0]["rr_full_name"].(string))
    d.Set("type", buf[0]["rr_type"].(string))
    d.Set("value", buf[0]["value1"].(string))
    d.Set("ttl", ttl)

    return []*schema.ResourceData{d}, nil
  }

  if (len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      // Log the error
      log.Printf("[DEBUG] SOLIDServer - Unable to find RR: %s (%s)", d.Get("name"), errmsg)
    }
  } else {
    // Log the error
    log.Printf("[DEBUG] SOLIDServer - Unable to find RR (oid): %s", d.Id())
  }

  // Do not unset the local ID to avoid inconsistency

  // Reporting a failure
  return nil, fmt.Errorf("SOLIDServer - Unable to find RR: %s", d.Get("name").(string))

}

func resourcednsrrCreate(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("dns_name", d.Get("dnsserver").(string))
  parameters.Add("rr_name", d.Get("name").(string))
  parameters.Add("rr_type", strings.ToUpper(d.Get("type").(string)))
  parameters.Add("value1", d.Get("value").(string))
  parameters.Add("rr_ttl", strconv.Itoa(d.Get("ttl").(int)))

  // Sending the creation request
  http_resp, body, _ := s.Request("post", "rest/dns_rr_add", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 201 && len(buf) > 0) {
    if oid, oid_exist := buf[0]["ret_oid"].(string); (oid_exist) {
      log.Printf("[DEBUG] SOLIDServer - Created RR (oid): %s", oid)
      d.SetId(oid)
      return nil
    }
  }

  // Reporting a failure
  return fmt.Errorf("SOLIDServer - Unable to create RR record: %s", d.Get("name").(string))
}

func resourcednsrrUpdate(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("rr_id", d.Id())
  parameters.Add("dns_name", d.Get("dnsserver").(string))
  parameters.Add("rr_name", d.Get("name").(string))
  parameters.Add("rr_type", strings.ToUpper(d.Get("type").(string)))
  parameters.Add("value1", d.Get("value").(string))
  parameters.Add("rr_ttl", strconv.Itoa(d.Get("ttl").(int)))

  // Sending the update request
  http_resp, body, _ := s.Request("put", "rest/dns_rr_add", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 200 && len(buf) > 0) {
    if oid, oid_exist := buf[0]["ret_oid"].(string); (oid_exist) {
      log.Printf("[DEBUG] SOLIDServer - Updated RR (oid): %s", oid)
      d.SetId(oid)
      return nil
    }
  }

  // Reporting a failure
  return fmt.Errorf("SOLIDServer - Unable to update RR: %s", d.Get("name").(string))
}

func resourcednsrrDelete(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("rr_id", d.Id())

  // Sending the deletion request
  http_resp, body, _ := s.Request("delete", "rest/dns_rr_delete", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode != 204 && len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      log.Printf("[DEBUG] SOLIDServer - Unable to delete RR: %s (%s)", d.Get("name"), errmsg)
    }
  }

  // Log deletion
  log.Printf("[DEBUG] SOLIDServer - Deleted RR (oid): %s", d.Id())

  // Unset local ID
  d.SetId("")

  return nil
}

func resourcednsrrRead(d *schema.ResourceData, meta interface{}) error {
  s := meta.(*SOLIDserver)

  // Building parameters
  parameters := url.Values{}
  parameters.Add("rr_id", d.Id())

  // Sending the read request
  http_resp, body, _ := s.Request("get", "rest/dns_rr_info", &parameters)

  var buf [](map[string]interface{})
  json.Unmarshal([]byte(body), &buf)

  // Checking the answer
  if (http_resp.StatusCode == 200 && len(buf) > 0) {
    ttl, _ := strconv.Atoi(buf[0]["ttl"].(string))

    d.Set("dnsserver", buf[0]["dns_name"].(string))
    d.Set("name", buf[0]["rr_full_name"].(string))
    d.Set("type", buf[0]["rr_type"].(string))
    d.Set("value", buf[0]["value1"].(string))
    d.Set("ttl", ttl)

    return nil
  }

  if (len(buf) > 0) {
    if errmsg, err_exist := buf[0]["errmsg"].(string); (err_exist) {
      // Log the error
      log.Printf("[DEBUG] SOLIDServer - Unable to find RR: %s (%s)", d.Get("name"), errmsg)
    }
  } else {
    // Log the error
    log.Printf("[DEBUG] SOLIDServer - Unable to find RR (oid): %s", d.Id())
  }

  // Do not unset the local ID to avoid inconsistency

  // Reporting a failure
  return fmt.Errorf("SOLIDServer - Unable to find RR: %s", d.Get("name").(string))
}
<|endoftext|>


<commit_before>package storage

import (
	"io"
	"os"
	"path"

	"github.com/anacrolix/missinggo"

	"github.com/anacrolix/torrent/metainfo"
)

type pieceFileStorage struct {
	fs missinggo.FileStore
}

func NewPieceFileStorage(fs missinggo.FileStore) I {
	return &pieceFileStorage{
		fs: fs,
	}
}

type pieceFileTorrentStorage struct {
	s *pieceFileStorage
}

func (me *pieceFileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
	return &pieceFileTorrentStorage{me}, nil
}

func (me *pieceFileTorrentStorage) Close() error {
	return nil
}

func (me *pieceFileTorrentStorage) Piece(p metainfo.Piece) Piece {
	return pieceFileTorrentStoragePiece{me, p, me.s.fs}
}

type pieceFileTorrentStoragePiece struct {
	ts *pieceFileTorrentStorage
	p  metainfo.Piece
	fs missinggo.FileStore
}

func (me pieceFileTorrentStoragePiece) completedPath() string {
	return path.Join("completed", me.p.Hash().HexString())
}

func (me pieceFileTorrentStoragePiece) incompletePath() string {
	return path.Join("incomplete", me.p.Hash().HexString())
}

func (me pieceFileTorrentStoragePiece) GetIsComplete() bool {
	fi, err := me.ts.s.fs.Stat(me.completedPath())
	return err == nil && fi.Size() == me.p.Length()
}

func (me pieceFileTorrentStoragePiece) MarkComplete() error {
	return me.fs.Rename(me.incompletePath(), me.completedPath())
}

func (me pieceFileTorrentStoragePiece) openFile() (f missinggo.File, err error) {
	f, err = me.fs.OpenFile(me.completedPath(), os.O_RDONLY)
	if err == nil {
		var fi os.FileInfo
		fi, err = f.Stat()
		if err == nil && fi.Size() == me.p.Length() {
			return
		}
		f.Close()
	} else if !os.IsNotExist(err) {
		return
	}
	f, err = me.fs.OpenFile(me.incompletePath(), os.O_RDONLY)
	if os.IsNotExist(err) {
		err = io.ErrUnexpectedEOF
	}
	return
}

func (me pieceFileTorrentStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
	f, err := me.openFile()
	if err != nil {
		return
	}
	defer f.Close()
	missinggo.LimitLen(&b, me.p.Length()-off)
	n, err = f.ReadAt(b, off)
	off += int64(n)
	if off >= me.p.Length() {
		err = io.EOF
	} else if err == io.EOF {
		err = io.ErrUnexpectedEOF
	}
	return
}

func (me pieceFileTorrentStoragePiece) WriteAt(b []byte, off int64) (n int, err error) {
	f, err := me.fs.OpenFile(me.incompletePath(), os.O_WRONLY|os.O_CREATE)
	if err != nil {
		return
	}
	defer f.Close()
	missinggo.LimitLen(&b, me.p.Length()-off)
	return f.WriteAt(b, off)
}
<commit_msg>piece file storage: Don't write to completed pieces<commit_after>package storage

import (
	"errors"
	"io"
	"os"
	"path"

	"github.com/anacrolix/missinggo"

	"github.com/anacrolix/torrent/metainfo"
)

type pieceFileStorage struct {
	fs missinggo.FileStore
}

func NewPieceFileStorage(fs missinggo.FileStore) I {
	return &pieceFileStorage{
		fs: fs,
	}
}

type pieceFileTorrentStorage struct {
	s *pieceFileStorage
}

func (me *pieceFileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
	return &pieceFileTorrentStorage{me}, nil
}

func (me *pieceFileTorrentStorage) Close() error {
	return nil
}

func (me *pieceFileTorrentStorage) Piece(p metainfo.Piece) Piece {
	return pieceFileTorrentStoragePiece{me, p, me.s.fs}
}

type pieceFileTorrentStoragePiece struct {
	ts *pieceFileTorrentStorage
	p  metainfo.Piece
	fs missinggo.FileStore
}

func (me pieceFileTorrentStoragePiece) completedPath() string {
	return path.Join("completed", me.p.Hash().HexString())
}

func (me pieceFileTorrentStoragePiece) incompletePath() string {
	return path.Join("incomplete", me.p.Hash().HexString())
}

func (me pieceFileTorrentStoragePiece) GetIsComplete() bool {
	fi, err := me.ts.s.fs.Stat(me.completedPath())
	return err == nil && fi.Size() == me.p.Length()
}

func (me pieceFileTorrentStoragePiece) MarkComplete() error {
	return me.fs.Rename(me.incompletePath(), me.completedPath())
}

func (me pieceFileTorrentStoragePiece) openFile() (f missinggo.File, err error) {
	f, err = me.fs.OpenFile(me.completedPath(), os.O_RDONLY)
	if err == nil {
		var fi os.FileInfo
		fi, err = f.Stat()
		if err == nil && fi.Size() == me.p.Length() {
			return
		}
		f.Close()
	} else if !os.IsNotExist(err) {
		return
	}
	f, err = me.fs.OpenFile(me.incompletePath(), os.O_RDONLY)
	if os.IsNotExist(err) {
		err = io.ErrUnexpectedEOF
	}
	return
}

func (me pieceFileTorrentStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
	f, err := me.openFile()
	if err != nil {
		return
	}
	defer f.Close()
	missinggo.LimitLen(&b, me.p.Length()-off)
	n, err = f.ReadAt(b, off)
	off += int64(n)
	if off >= me.p.Length() {
		err = io.EOF
	} else if err == io.EOF {
		err = io.ErrUnexpectedEOF
	}
	return
}

func (me pieceFileTorrentStoragePiece) WriteAt(b []byte, off int64) (n int, err error) {
	if me.GetIsComplete() {
		err = errors.New("piece completed")
		return
	}
	f, err := me.fs.OpenFile(me.incompletePath(), os.O_WRONLY|os.O_CREATE)
	if err != nil {
		return
	}
	defer f.Close()
	missinggo.LimitLen(&b, me.p.Length()-off)
	return f.WriteAt(b, off)
}
<|endoftext|>