<commit_before>package main

import (
	"context"
	"database/sql"
	"log"
	"math"
	"net/http"
	"sync"
	"time"
)

type RoomNameMap map[RoomID]string
type RoomGroupMap map[BuildingName]map[FloorID][]RoomID

const (
	INTERVAL     = 1 * time.Minute
	CACHE_EXPIRE = 3 * time.Minute
)

type RoomStatus struct {
	RoomID  RoomID         `json:"id"`
	Sensors []SensorStatus `json:"sensors"`

	Hot     uint64 `json:"hot"`
	Comfort uint64 `json:"comfort"`
	Cold    uint64 `json:"cold"`
	lock    sync.RWMutex
}

type MyVote struct {
	Vote      VoteChoice `json:"vote"`
	Timestamp int64      `json:"timestamp"`
}

type RoomStatusManager struct {
	db        *sql.DB
	thingworx *ThingWorxClient

	sensorCache map[RoomID]map[ThingName]SensorStatus
	cacheLock   sync.RWMutex
}

type RoomStatusTx struct {
	rsm *RoomStatusManager
	tx  *sql.Tx

	// nilになる場合があるため、使用前に必ずnilチェックを行うこと。
	s *Session
}

type SensorStatus struct {
	Temperature float64 `json:"temperature"`
	Humidity    float64 `json:"humidity"`
	lastUpdated int64   `json:"lastUpdated"` // unix time (second)

	expire time.Time
}

func NewRoomStatusManager(db *sql.DB, thingworx *ThingWorxClient, ctx context.Context) *RoomStatusManager {
	// create RSM
	rs := &RoomStatusManager{}
	rs.db = db
	rs.thingworx = thingworx
	rs.sensorCache = make(map[RoomID]map[ThingName]SensorStatus)

	go rs.cacheUpdater(ctx)
	return rs
}

func (rsm *RoomStatusManager) GetTx(w http.ResponseWriter, req *http.Request, new bool) (*RoomStatusTx, error) {
	tx, err := rsm.db.Begin()
	if err != nil {
		return nil, err
	}
	s := GetSession(w, req, tx)
	if s == nil && new {
		s, err = NewSession(w, req, tx)
		if err != nil {
			defer tx.Rollback()
			return nil, err
		}
	}
	return &RoomStatusTx{
		rsm: rsm,
		tx:  tx,
		s:   s,
	}, nil
}

func (rst *RoomStatusTx) Rollback() error {
	return rst.tx.Rollback()
}

func (rst *RoomStatusTx) Commit() error {
	return rst.tx.Commit()
}

func (rst *RoomStatusTx) GetRoomName(id RoomID) (name string, err error) {
	err = rst.tx.QueryRow(
		`SELECT name FROM room
		WHERE room_id=?`,
		id,
	).Scan(&name)
	return
}

// 投票内容を取得する。未投票の場合はnilを返す
func (rst *RoomStatusTx) GetMyVote(id RoomID) (vote *MyVote, err error) {
	var v Vote

	if rst.s == nil {
		// セッションがnilなので、未投票とみなす
		return nil, nil
	}

	if err = rst.tx.QueryRow(
		`SELECT choice, timestamp FROM vote
			WHERE session_id=? AND room_id=?`,
		rst.s.SessionID, id,
	).Scan((*string)(&v.Choice), &v.Timestamp); err != nil {
		if err == sql.ErrNoRows {
			// 未投票の状態。
			err = nil
			return
		}
		return
	}

	vote = &MyVote{
		Vote:      v.Choice,
		Timestamp: v.Timestamp.Unix(),
	}
	return vote, err
}

func (rst *RoomStatusTx) GetStatus(id RoomID) (*RoomStatus, error) {
	rs := &RoomStatus{
		RoomID: id,
	}

	var ok bool
	rs.Sensors, ok = rst.rsm.getSensorStatusFromCache(id)
	if !ok {
		// センサーの状態を更新できていない状態。
		rs.Sensors = []SensorStatus{}
	}

	rows, err := rst.tx.Query(
		`SELECT vote.choice, count(vote.vote_id) FROM vote
		NATURAL JOIN session
		WHERE vote.room_id=? AND session.expire>=?
		GROUP BY vote.choice`,
		id, time.Now(),
	)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var choice VoteChoice
		var count uint64
		if err := rows.Scan((*string)(&choice), &count); err != nil {
			return nil, err
		}
		switch choice {
		case Hot:
			rs.Hot = count
		case Comfort:
			rs.Comfort = count
		case Cold:
			rs.Cold = count
		}
	}
	return rs, nil
}

func (rst *RoomStatusTx) Vote(id RoomID, choice VoteChoice) error {
	if rst.s == nil {
		panic("session must not nil")
	}

	vote := Vote{
		RoomID: id,
		S:      rst.s,
	}

	if err := rst.tx.QueryRow(
		`SELECT vote_id FROM vote
		WHERE session_id=? AND room_id=?`,
		rst.s.SessionID, id,
	).Scan(&vote.VoteID); err != nil {
		switch err {
		case sql.ErrNoRows:
			// 未投票であることを表す、0を代入
			vote.VoteID = VoteID(0)
		default:
			return err
		}
	}

	return vote.UpdateChoice(rst.tx, choice)
}

func (rst *RoomStatusTx) GetAllRoomsInfo() (names RoomNameMap, groups RoomGroupMap, err error) {
	// NOTE: roomテーブルの行数は少ないことを想定しているため、テーブルスキャンをしている。
	{
		names = make(RoomNameMap)
		var rows *sql.Rows
		rows, err = rst.tx.Query(`
			SELECT room_id, name FROM room
		`)
		if err != nil {
			return
		}
		defer rows.Close()
		for rows.Next() {
			var id RoomID
			var name string
			if err = rows.Scan(&id, &name); err != nil {
				return
			}
			names[id] = name
		}
	}

	{
		groups = make(RoomGroupMap)
		var rows *sql.Rows
		rows, err = rst.tx.Query(`
			SELECT building_name, floor, room_id FROM room
			GROUP BY building_name, floor, room_id
		`)
		defer rows.Close()
		for rows.Next() {
			var bname BuildingName
			var floor FloorID
			var id RoomID
			if err = rows.Scan((*string)(&bname), &floor, &id); err != nil {
				return
			}

			if _, ok := groups[bname]; !ok {
				groups[bname] = make(map[FloorID][]RoomID)
			}
			if _, ok := groups[bname][floor]; !ok {
				groups[bname][floor] = make([]RoomID, 0)
			}
			groups[bname][floor] = append(groups[bname][floor], id)
		}
	}
	return
}

func (rsm *RoomStatusManager) getSensorStatusFromCache(id RoomID) ([]SensorStatus, bool) {
	rsm.cacheLock.RLock()
	defer rsm.cacheLock.RUnlock()

	cache, ok := rsm.sensorCache[id]
	if ok {
		array := make([]SensorStatus, 0, len(cache))
		for i := range cache {
			if cache[i].expire.After(time.Now()) {
				array = append(array, cache[i])
			}
		}
		return array, len(array) > 0
	}
	return []SensorStatus{}, false
}

// すべてのセンサーの状態をキャッシュする
func (rsm *RoomStatusManager) cacheUpdater(ctx context.Context) {
	log.Println("starting cacheUpdater")

	tick := time.NewTicker(INTERVAL)
	for {
		log.Println("update all sensor statuses")
		for _, err := range rsm.updateAllSensorStatuses() {
			log.Println(err)
		}

		log.Println("clean up expired sessions")
		if err := rsm.cleanUpExpiredSessions(); err != nil {
			log.Println(err)
		}

		select {
		case <-ctx.Done():
			return
		case <-tick.C:
		}
	}
}

func (rsm *RoomStatusManager) updateAllSensorStatuses() []error {
	errCh := make(chan error)
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		tx, err := rsm.db.Begin()
		if err != nil {
			errCh <- err
			return
		}
		defer tx.Rollback()

		rows, err := tx.Query(
			`SELECT room_id, thing_name FROM thing`,
		)
		if err != nil {
			errCh <- err
			return
		}

		for rows.Next() {
			var id RoomID
			var name ThingName
			rows.Scan(&id, (*string)(&name))

			// start async update
			wg.Add(1)
			go func(id RoomID, name ThingName) {
				defer wg.Done()
				if err := rsm.updateSensorStatus(id, name); err != nil {
					errCh <- err
					return
				}
			}(id, name)
		}
	}()

	go func() {
		wg.Wait()
		close(errCh)
	}()

	errs := []error{}
	for err := range errCh {
		errs = append(errs, err)
	}
	return errs
}

// センサーで測定した部屋の状態を、DBに反映する。
func (rsm *RoomStatusManager) updateSensorStatus(id RoomID, thingName ThingName) error {
	var stat SensorStatus

	prop, err := rsm.thingworx.Properties(thingName)
	if err != nil {
		return err
	}
	stat.Temperature, err = prop.M("temperature").Float64()
	if err != nil {
		return err
	}
	stat.Humidity, err = prop.M("humidity").Float64()
	if err != nil {
		return err
	}
	stat.lastUpdated, err = prop.M("lastUpdated").Int64()
	if err != nil {
		return err
	}
	// ミリ秒単位から秒単位に変換
	stat.lastUpdated /= 1000
	stat.expire = time.Now().Add(CACHE_EXPIRE)

	// 最終更新時刻が現在時刻から60秒以上ずれていたら、未接続と見なす
	if math.Abs(float64(time.Now().Unix()-stat.lastUpdated)) >= 60 {
		log.Printf("WARN: \"%s\" is not connected. now=%d, lastUpdated=%d", thingName, time.Now().Unix(), stat.lastUpdated)
		return nil
	}

	rsm.cacheLock.Lock()
	defer rsm.cacheLock.Unlock()
	if _, ok := rsm.sensorCache[id]; !ok {
		rsm.sensorCache[id] = map[ThingName]SensorStatus{}
	}
	rsm.sensorCache[id][thingName] = stat
	return nil
}

func (rsm *RoomStatusManager) cleanUpExpiredSessions() error {
	tx, err := rsm.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.Exec(
		`DELETE FROM session WHERE expire<?`,
		time.Now(),
	); err != nil {
		return err
	}
	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}
<commit_msg>Revert "fixup! fix a parameter"<commit_after>package main

import (
	"context"
	"database/sql"
	"log"
	"math"
	"net/http"
	"sync"
	"time"
)

type RoomNameMap map[RoomID]string
type RoomGroupMap map[BuildingName]map[FloorID][]RoomID

const (
	INTERVAL     = 1 * time.Minute
	CACHE_EXPIRE = 3 * time.Minute
)

type RoomStatus struct {
	RoomID  RoomID         `json:"id"`
	Sensors []SensorStatus `json:"sensors"`

	Hot     uint64 `json:"hot"`
	Comfort uint64 `json:"comfort"`
	Cold    uint64 `json:"cold"`
	lock    sync.RWMutex
}

type MyVote struct {
	Vote      VoteChoice `json:"vote"`
	Timestamp int64      `json:"timestamp"`
}

type RoomStatusManager struct {
	db        *sql.DB
	thingworx *ThingWorxClient

	sensorCache map[RoomID]map[ThingName]SensorStatus
	cacheLock   sync.RWMutex
}

type RoomStatusTx struct {
	rsm *RoomStatusManager
	tx  *sql.Tx

	// nilになる場合があるため、使用前に必ずnilチェックを行うこと。
	s *Session
}

type SensorStatus struct {
	Temperature float64 `json:"temperature"`
	Humidity    float64 `json:"humidity"`
	lastUpdated int64   `json:"lastUpdated"` // unix time (second)

	expire time.Time
}

func NewRoomStatusManager(db *sql.DB, thingworx *ThingWorxClient, ctx context.Context) *RoomStatusManager {
	// create RSM
	rs := &RoomStatusManager{}
	rs.db = db
	rs.thingworx = thingworx
	rs.sensorCache = make(map[RoomID]map[ThingName]SensorStatus)

	go rs.cacheUpdater(ctx)
	return rs
}

func (rsm *RoomStatusManager) GetTx(w http.ResponseWriter, req *http.Request, new bool) (*RoomStatusTx, error) {
	tx, err := rsm.db.Begin()
	if err != nil {
		return nil, err
	}
	s := GetSession(w, req, tx)
	if s == nil && new {
		s, err = NewSession(w, req, tx)
		if err != nil {
			defer tx.Rollback()
			return nil, err
		}
	}
	return &RoomStatusTx{
		rsm: rsm,
		tx:  tx,
		s:   s,
	}, nil
}

func (rst *RoomStatusTx) Rollback() error {
	return rst.tx.Rollback()
}

func (rst *RoomStatusTx) Commit() error {
	return rst.tx.Commit()
}

func (rst *RoomStatusTx) GetRoomName(id RoomID) (name string, err error) {
	err = rst.tx.QueryRow(
		`SELECT name FROM room
		WHERE room_id=?`,
		id,
	).Scan(&name)
	return
}

// 投票内容を取得する。未投票の場合はnilを返す
func (rst *RoomStatusTx) GetMyVote(id RoomID) (vote *MyVote, err error) {
	var v Vote

	if rst.s == nil {
		// セッションがnilなので、未投票とみなす
		return nil, nil
	}

	if err = rst.tx.QueryRow(
		`SELECT choice, timestamp FROM vote
			WHERE session_id=? AND room_id=?`,
		rst.s.SessionID, id,
	).Scan((*string)(&v.Choice), &v.Timestamp); err != nil {
		if err == sql.ErrNoRows {
			// 未投票の状態。
			err = nil
			return
		}
		return
	}

	vote = &MyVote{
		Vote:      v.Choice,
		Timestamp: v.Timestamp.Unix(),
	}
	return vote, err
}

func (rst *RoomStatusTx) GetStatus(id RoomID) (*RoomStatus, error) {
	rs := &RoomStatus{
		RoomID: id,
	}

	var ok bool
	rs.Sensors, ok = rst.rsm.getSensorStatusFromCache(id)
	if !ok {
		// センサーの状態を更新できていない状態。
		rs.Sensors = []SensorStatus{}
	}

	rows, err := rst.tx.Query(
		`SELECT vote.choice, count(vote.vote_id) FROM vote
		NATURAL JOIN session
		WHERE vote.room_id=? AND session.expire>=?
		GROUP BY vote.choice`,
		id, time.Now(),
	)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var choice VoteChoice
		var count uint64
		if err := rows.Scan((*string)(&choice), &count); err != nil {
			return nil, err
		}
		switch choice {
		case Hot:
			rs.Hot = count
		case Comfort:
			rs.Comfort = count
		case Cold:
			rs.Cold = count
		}
	}
	return rs, nil
}

func (rst *RoomStatusTx) Vote(id RoomID, choice VoteChoice) error {
	if rst.s == nil {
		panic("session must not nil")
	}

	vote := Vote{
		RoomID: id,
		S:      rst.s,
	}

	if err := rst.tx.QueryRow(
		`SELECT vote_id FROM vote
		WHERE session_id=? AND room_id=?`,
		rst.s.SessionID, id,
	).Scan(&vote.VoteID); err != nil {
		switch err {
		case sql.ErrNoRows:
			// 未投票であることを表す、0を代入
			vote.VoteID = VoteID(0)
		default:
			return err
		}
	}

	return vote.UpdateChoice(rst.tx, choice)
}

func (rst *RoomStatusTx) GetAllRoomsInfo() (names RoomNameMap, groups RoomGroupMap, err error) {
	// NOTE: roomテーブルの行数は少ないことを想定しているため、テーブルスキャンをしている。
	{
		names = make(RoomNameMap)
		var rows *sql.Rows
		rows, err = rst.tx.Query(`
			SELECT room_id, name FROM room
		`)
		if err != nil {
			return
		}
		defer rows.Close()
		for rows.Next() {
			var id RoomID
			var name string
			if err = rows.Scan(&id, &name); err != nil {
				return
			}
			names[id] = name
		}
	}

	{
		groups = make(RoomGroupMap)
		var rows *sql.Rows
		rows, err = rst.tx.Query(`
			SELECT building_name, floor, room_id FROM room
			GROUP BY building_name, floor, room_id
		`)
		defer rows.Close()
		for rows.Next() {
			var bname BuildingName
			var floor FloorID
			var id RoomID
			if err = rows.Scan((*string)(&bname), &floor, &id); err != nil {
				return
			}

			if _, ok := groups[bname]; !ok {
				groups[bname] = make(map[FloorID][]RoomID)
			}
			if _, ok := groups[bname][floor]; !ok {
				groups[bname][floor] = make([]RoomID, 0)
			}
			groups[bname][floor] = append(groups[bname][floor], id)
		}
	}
	return
}

func (rsm *RoomStatusManager) getSensorStatusFromCache(id RoomID) ([]SensorStatus, bool) {
	rsm.cacheLock.RLock()
	defer rsm.cacheLock.RUnlock()

	cache, ok := rsm.sensorCache[id]
	if ok {
		array := make([]SensorStatus, 0, len(cache))
		for i := range cache {
			if cache[i].expire.After(time.Now()) {
				array = append(array, cache[i])
			}
		}
		return array, len(array) > 0
	}
	return []SensorStatus{}, false
}

// すべてのセンサーの状態をキャッシュする
func (rsm *RoomStatusManager) cacheUpdater(ctx context.Context) {
	log.Println("starting cacheUpdater")

	tick := time.NewTicker(INTERVAL)
	for {
		log.Println("update all sensor statuses")
		for _, err := range rsm.updateAllSensorStatuses() {
			log.Println(err)
		}

		log.Println("clean up expired sessions")
		if err := rsm.cleanUpExpiredSessions(); err != nil {
			log.Println(err)
		}

		select {
		case <-ctx.Done():
			return
		case <-tick.C:
		}
	}
}

func (rsm *RoomStatusManager) updateAllSensorStatuses() []error {
	errCh := make(chan error)
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		tx, err := rsm.db.Begin()
		if err != nil {
			errCh <- err
			return
		}
		defer tx.Rollback()

		rows, err := tx.Query(
			`SELECT room_id, thing_name FROM thing`,
		)
		if err != nil {
			errCh <- err
			return
		}

		for rows.Next() {
			var id RoomID
			var name ThingName
			rows.Scan(&id, (*string)(&name))

			// start async update
			wg.Add(1)
			go func(id RoomID, name ThingName) {
				defer wg.Done()
				if err := rsm.updateSensorStatus(id, name); err != nil {
					errCh <- err
					return
				}
			}(id, name)
		}
	}()

	go func() {
		wg.Wait()
		close(errCh)
	}()

	errs := []error{}
	for err := range errCh {
		errs = append(errs, err)
	}
	return errs
}

// センサーで測定した部屋の状態を、DBに反映する。
func (rsm *RoomStatusManager) updateSensorStatus(id RoomID, thingName ThingName) error {
	var stat SensorStatus

	prop, err := rsm.thingworx.Properties(thingName)
	if err != nil {
		return err
	}
	stat.Temperature, err = prop.M("temperature").Float64()
	if err != nil {
		return err
	}
	stat.Humidity, err = prop.M("humidity").Float64()
	if err != nil {
		return err
	}
	stat.lastUpdated, err = prop.M("lastUpdated").Int64()
	if err != nil {
		return err
	}
	// ミリ秒単位から秒単位に変換
	stat.lastUpdated /= 1000
	stat.expire = time.Now().Add(CACHE_EXPIRE)

	// 最終更新時刻が現在時刻から60秒以上ずれていたら、未接続と見なす
	if math.Abs(float64(time.Now().Unix()-stat.lastUpdated)) < 60 {
		log.Printf("WARN: \"%s\" is not connected. now=%d, lastUpdated=%d", thingName, time.Now().Unix(), stat.lastUpdated)
		return nil
	}

	rsm.cacheLock.Lock()
	defer rsm.cacheLock.Unlock()
	if _, ok := rsm.sensorCache[id]; !ok {
		rsm.sensorCache[id] = map[ThingName]SensorStatus{}
	}
	rsm.sensorCache[id][thingName] = stat
	return nil
}

func (rsm *RoomStatusManager) cleanUpExpiredSessions() error {
	tx, err := rsm.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.Exec(
		`DELETE FROM session WHERE expire<?`,
		time.Now(),
	); err != nil {
		return err
	}
	if err := tx.Commit(); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 The go-hep 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 rootio

import (
	"fmt"
	"io"
	"reflect"
	"time"
)

// noKeyError is the error returned when a rootio.Key could not be found.
type noKeyError struct {
	key string
	obj Named
}

func (err noKeyError) Error() string {
	return fmt.Sprintf("rootio: %s: could not find key %q", err.obj.Name(), err.key)
}

// Key is a key (a label) in a ROOT file
//
//  The Key class includes functions to book space on a file,
//   to create I/O buffers, to fill these buffers
//   to compress/uncompress data buffers.
//
//  Before saving (making persistent) an object on a file, a key must
//  be created. The key structure contains all the information to
//  uniquely identify a persistent object on a file.
//  The Key class is used by ROOT:
//    - to write an object in the Current Directory
//    - to write a new ntuple buffer
type Key struct {
	f *File // underlying file

	bytes    int32     // number of bytes for the compressed object+key
	version  int16     // version of the Key struct
	objlen   int32     // length of uncompressed object
	datetime time.Time // Date/Time when the object was written
	keylen   int32     // number of bytes for the Key struct
	cycle    int16     // cycle number of the object

	// address of the object on file (points to Key.bytes)
	// this is a redundant information used to cross-check
	// the data base integrity
	seekkey  int64
	seekpdir int64 // pointer to the directory supporting this object

	class string // object class name
	name  string // name of the object
	title string // title of the object

	buf []byte // buffer of the Key's value
	obj Object // Key's value
}

func (k *Key) Class() string {
	return k.class
}

func (k *Key) Name() string {
	return k.name
}

func (k *Key) Title() string {
	return k.title
}

func (k *Key) Cycle() int {
	return int(k.cycle)
}

// Value returns the data corresponding to the Key's value
func (k *Key) Value() interface{} {
	v, err := k.Object()
	if err != nil {
		panic(err)
	}
	return v
}

// Object returns the (ROOT) object corresponding to the Key's value.
func (k *Key) Object() (Object, error) {
	if k.obj != nil {
		return k.obj, nil
	}

	buf, err := k.Bytes()
	if err != nil {
		return nil, err
	}

	fct := Factory.Get(k.Class())
	if fct == nil {
		return nil, fmt.Errorf("rootio: no registered factory for class %q (key=%q)", k.Class(), k.Name())
	}

	v := fct()
	obj, ok := v.Interface().(Object)
	if !ok {
		return nil, fmt.Errorf("rootio: class %q does not implement rootio.Object (key=%q)", k.Class(), k.Name())
	}

	vv, ok := obj.(ROOTUnmarshaler)
	if !ok {
		return nil, fmt.Errorf("rootio: class %q does not implement rootio.ROOTUnmarshaler (key=%q)", k.Class(), k.Name())
	}

	err = vv.UnmarshalROOT(NewRBuffer(buf, nil, uint32(k.keylen), k.f))
	if err != nil {
		return nil, err
	}

	if vv, ok := obj.(SetFiler); ok {
		vv.SetFile(k.f)
	}
	if dir, ok := obj.(*tdirectory); ok {
		dir.file = k.f
		err = dir.readKeys()
		if err != nil {
			return nil, err
		}
	}

	k.obj = obj
	return obj, nil
}

// Bytes returns the buffer of bytes corresponding to the Key's value
func (k *Key) Bytes() ([]byte, error) {
	data, err := k.load(nil)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (k *Key) load(buf []byte) ([]byte, error) {
	if len(buf) < int(k.objlen) {
		buf = make([]byte, k.objlen)
	}
	if len(k.buf) > 0 {
		copy(buf, k.buf)
		return buf, nil
	}
	if k.isCompressed() {
		start := k.seekkey + int64(k.keylen)
		sr := io.NewSectionReader(k.f, start, int64(k.bytes)-int64(k.keylen))
		err := decompress(sr, buf)
		if err != nil {
			return nil, err
		}
		return buf, nil
	}
	start := k.seekkey + int64(k.keylen)
	r := io.NewSectionReader(k.f, start, int64(k.bytes))
	_, err := io.ReadFull(r, buf)
	if err != nil {
		return nil, err
	}
	return buf, nil
}

func (k *Key) isCompressed() bool {
	return k.objlen != k.bytes-k.keylen
}

// UnmarshalROOT decodes the content of data into the Key
func (k *Key) UnmarshalROOT(r *RBuffer) error {
	if r.Err() != nil {
		return r.Err()
	}

	k.bytes = r.ReadI32()
	if k.bytes < 0 {
		k.class = "[GAP]"
		return nil
	}

	k.version = r.ReadI16()
	k.objlen = r.ReadI32()
	k.datetime = datime2time(r.ReadU32())
	k.keylen = int32(r.ReadI16())
	k.cycle = r.ReadI16()

	if k.version > 1000 {
		k.seekkey = r.ReadI64()
		k.seekpdir = r.ReadI64()
	} else {
		k.seekkey = int64(r.ReadI32())
		k.seekpdir = int64(r.ReadI32())
	}

	k.class = r.ReadString()
	k.name = r.ReadString()
	k.title = r.ReadString()

	//k.pdat = data

	return r.Err()
}

func init() {
	f := func() reflect.Value {
		o := &Key{}
		return reflect.ValueOf(o)
	}
	Factory.add("TKey", f)
	Factory.add("*rootio.Key", f)
}

var _ Object = (*Key)(nil)
var _ Named = (*Key)(nil)
var _ ROOTUnmarshaler = (*Key)(nil)
<commit_msg>rootio: add more context to Key.Value error<commit_after>// Copyright 2017 The go-hep 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 rootio

import (
	"fmt"
	"io"
	"reflect"
	"time"
)

// noKeyError is the error returned when a rootio.Key could not be found.
type noKeyError struct {
	key string
	obj Named
}

func (err noKeyError) Error() string {
	return fmt.Sprintf("rootio: %s: could not find key %q", err.obj.Name(), err.key)
}

// Key is a key (a label) in a ROOT file
//
//  The Key class includes functions to book space on a file,
//   to create I/O buffers, to fill these buffers
//   to compress/uncompress data buffers.
//
//  Before saving (making persistent) an object on a file, a key must
//  be created. The key structure contains all the information to
//  uniquely identify a persistent object on a file.
//  The Key class is used by ROOT:
//    - to write an object in the Current Directory
//    - to write a new ntuple buffer
type Key struct {
	f *File // underlying file

	bytes    int32     // number of bytes for the compressed object+key
	version  int16     // version of the Key struct
	objlen   int32     // length of uncompressed object
	datetime time.Time // Date/Time when the object was written
	keylen   int32     // number of bytes for the Key struct
	cycle    int16     // cycle number of the object

	// address of the object on file (points to Key.bytes)
	// this is a redundant information used to cross-check
	// the data base integrity
	seekkey  int64
	seekpdir int64 // pointer to the directory supporting this object

	class string // object class name
	name  string // name of the object
	title string // title of the object

	buf []byte // buffer of the Key's value
	obj Object // Key's value
}

func (k *Key) Class() string {
	return k.class
}

func (k *Key) Name() string {
	return k.name
}

func (k *Key) Title() string {
	return k.title
}

func (k *Key) Cycle() int {
	return int(k.cycle)
}

// Value returns the data corresponding to the Key's value
func (k *Key) Value() interface{} {
	v, err := k.Object()
	if err != nil {
		panic(fmt.Errorf("error loading payload for %q: %v", k.Name(), err))
	}
	return v
}

// Object returns the (ROOT) object corresponding to the Key's value.
func (k *Key) Object() (Object, error) {
	if k.obj != nil {
		return k.obj, nil
	}

	buf, err := k.Bytes()
	if err != nil {
		return nil, err
	}

	fct := Factory.Get(k.Class())
	if fct == nil {
		return nil, fmt.Errorf("rootio: no registered factory for class %q (key=%q)", k.Class(), k.Name())
	}

	v := fct()
	obj, ok := v.Interface().(Object)
	if !ok {
		return nil, fmt.Errorf("rootio: class %q does not implement rootio.Object (key=%q)", k.Class(), k.Name())
	}

	vv, ok := obj.(ROOTUnmarshaler)
	if !ok {
		return nil, fmt.Errorf("rootio: class %q does not implement rootio.ROOTUnmarshaler (key=%q)", k.Class(), k.Name())
	}

	err = vv.UnmarshalROOT(NewRBuffer(buf, nil, uint32(k.keylen), k.f))
	if err != nil {
		return nil, err
	}

	if vv, ok := obj.(SetFiler); ok {
		vv.SetFile(k.f)
	}
	if dir, ok := obj.(*tdirectory); ok {
		dir.file = k.f
		err = dir.readKeys()
		if err != nil {
			return nil, err
		}
	}

	k.obj = obj
	return obj, nil
}

// Bytes returns the buffer of bytes corresponding to the Key's value
func (k *Key) Bytes() ([]byte, error) {
	data, err := k.load(nil)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (k *Key) load(buf []byte) ([]byte, error) {
	if len(buf) < int(k.objlen) {
		buf = make([]byte, k.objlen)
	}
	if len(k.buf) > 0 {
		copy(buf, k.buf)
		return buf, nil
	}
	if k.isCompressed() {
		start := k.seekkey + int64(k.keylen)
		sr := io.NewSectionReader(k.f, start, int64(k.bytes)-int64(k.keylen))
		err := decompress(sr, buf)
		if err != nil {
			return nil, err
		}
		return buf, nil
	}
	start := k.seekkey + int64(k.keylen)
	r := io.NewSectionReader(k.f, start, int64(k.bytes))
	_, err := io.ReadFull(r, buf)
	if err != nil {
		return nil, err
	}
	return buf, nil
}

func (k *Key) isCompressed() bool {
	return k.objlen != k.bytes-k.keylen
}

// UnmarshalROOT decodes the content of data into the Key
func (k *Key) UnmarshalROOT(r *RBuffer) error {
	if r.Err() != nil {
		return r.Err()
	}

	k.bytes = r.ReadI32()
	if k.bytes < 0 {
		k.class = "[GAP]"
		return nil
	}

	k.version = r.ReadI16()
	k.objlen = r.ReadI32()
	k.datetime = datime2time(r.ReadU32())
	k.keylen = int32(r.ReadI16())
	k.cycle = r.ReadI16()

	if k.version > 1000 {
		k.seekkey = r.ReadI64()
		k.seekpdir = r.ReadI64()
	} else {
		k.seekkey = int64(r.ReadI32())
		k.seekpdir = int64(r.ReadI32())
	}

	k.class = r.ReadString()
	k.name = r.ReadString()
	k.title = r.ReadString()

	//k.pdat = data

	return r.Err()
}

func init() {
	f := func() reflect.Value {
		o := &Key{}
		return reflect.ValueOf(o)
	}
	Factory.add("TKey", f)
	Factory.add("*rootio.Key", f)
}

var _ Object = (*Key)(nil)
var _ Named = (*Key)(nil)
var _ ROOTUnmarshaler = (*Key)(nil)
<|endoftext|>


<commit_before>// Copyright (c) 2016 Tigera, 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 calc

import (
	log "github.com/Sirupsen/logrus"
	"github.com/projectcalico/felix/go/felix/config"
	"github.com/projectcalico/felix/go/felix/dispatcher"
	"github.com/projectcalico/felix/go/felix/proto"
	"github.com/projectcalico/libcalico-go/lib/backend/api"
	"github.com/projectcalico/libcalico-go/lib/backend/model"
	"time"
)

const (
	tickInterval    = 10 * time.Millisecond
	leakyBucketSize = 10
)

type AsyncCalcGraph struct {
	Dispatcher       *dispatcher.Dispatcher
	inputEvents      chan interface{}
	outputEvents     chan<- interface{}
	eventBuffer      *EventBuffer
	beenInSync       bool
	needToSendInSync bool

	flushTicks       <-chan time.Time
	flushLeakyBucket int
	dirty            bool
}

func NewAsyncCalcGraph(conf *config.Config, outputEvents chan<- interface{}) *AsyncCalcGraph {
	eventBuffer := NewEventBuffer(conf)
	dispatcher := NewCalculationGraph(eventBuffer, conf.FelixHostname)
	g := &AsyncCalcGraph{
		inputEvents:  make(chan interface{}, 10),
		outputEvents: outputEvents,
		Dispatcher:   dispatcher,
		eventBuffer:  eventBuffer,
	}
	eventBuffer.Callback = g.onEvent
	return g
}

func (acg *AsyncCalcGraph) OnUpdates(updates []model.Update) {
	log.Debugf("Got %v updates; queueing", len(updates))
	acg.inputEvents <- updates
}

func (acg *AsyncCalcGraph) OnStatusUpdated(status api.SyncStatus) {
	log.Debugf("Status updated: %v; queueing", status)
	acg.inputEvents <- status
}

func (acg *AsyncCalcGraph) loop() {
	log.Info("AsyncCalcGraph running")
	for {
		select {
		case update := <-acg.inputEvents:
			switch update := update.(type) {
			case []model.Update:
				// Update; send it to the dispatcher.
				log.Debug("Pulled []KVPair off channel")
				acg.Dispatcher.OnUpdates(update)
			case api.SyncStatus:
				// Sync status changed, check if we're now in-sync.
				log.WithField("status", update).Debug(
					"Pulled status update off channel")
				acg.Dispatcher.OnStatusUpdated(update)
				if update == api.InSync && !acg.beenInSync {
					log.Info("First time we've been in sync")
					acg.beenInSync = true
					acg.needToSendInSync = true
					acg.dirty = true
					if acg.flushLeakyBucket == 0 {
						// Force a flush.
						acg.flushLeakyBucket++
					}
				}
			default:
				log.Fatalf("Unexpected update: %#v", update)
			}
			acg.dirty = true
		case <-acg.flushTicks:
			// Timer tick: fill up the leaky bucket.
			if acg.flushLeakyBucket < leakyBucketSize {
				acg.flushLeakyBucket++
			}
		}
		acg.maybeFlush()
	}
}

// maybeFlush flushes the event buffer if: we know it's dirty and we're not throttled.
func (acg *AsyncCalcGraph) maybeFlush() {
	if !acg.dirty {
		return
	}
	if acg.flushLeakyBucket > 0 {
		log.Debug("Not throttled: flushing event buffer")
		acg.flushLeakyBucket--
		acg.eventBuffer.Flush()
		if acg.needToSendInSync {
			log.Info("First flush after becoming in sync, sending InSync message.")
			acg.onEvent(&proto.InSync{})
			acg.needToSendInSync = false
		}
		acg.dirty = false
	} else {
		log.Debug("Throttled: not flushing event buffer")
	}
}

func (acg *AsyncCalcGraph) onEvent(event interface{}) {
	log.Debug("Sending output event on channel")
	acg.outputEvents <- event
	log.Debug("Sent output event on channel")
}

func (acg *AsyncCalcGraph) Start() {
	log.Info("Starting AsyncCalcGraph")
	acg.flushTicks = time.Tick(tickInterval)
	go acg.loop()
}
<commit_msg>Add a couple of key metrics around resyncs.<commit_after>// Copyright (c) 2016 Tigera, 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 calc

import (
	log "github.com/Sirupsen/logrus"
	"github.com/projectcalico/felix/go/felix/config"
	"github.com/projectcalico/felix/go/felix/dispatcher"
	"github.com/projectcalico/felix/go/felix/proto"
	"github.com/projectcalico/libcalico-go/lib/backend/api"
	"github.com/projectcalico/libcalico-go/lib/backend/model"
	"github.com/prometheus/client_golang/prometheus"
	"time"
)

const (
	tickInterval    = 10 * time.Millisecond
	leakyBucketSize = 10
)

var (
	dataplaneStatusGauge = prometheus.NewGauge(prometheus.GaugeOpts{
		Name: "felix_resync_state",
		Help: "Current datastore state.",
	})
	resyncsStarted = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "felix_resyncs_started",
		Help: "Current datastore state.",
	})
	statusToGaugeValue = map[api.SyncStatus]float64{
		api.WaitForDatastore: 1,
		api.ResyncInProgress: 2,
		api.InSync:           3,
	}
)

func init() {
	prometheus.MustRegister(dataplaneStatusGauge)
	prometheus.MustRegister(resyncsStarted)
}

type AsyncCalcGraph struct {
	Dispatcher       *dispatcher.Dispatcher
	inputEvents      chan interface{}
	outputEvents     chan<- interface{}
	eventBuffer      *EventBuffer
	beenInSync       bool
	needToSendInSync bool

	flushTicks       <-chan time.Time
	flushLeakyBucket int
	dirty            bool
}

func NewAsyncCalcGraph(conf *config.Config, outputEvents chan<- interface{}) *AsyncCalcGraph {
	eventBuffer := NewEventBuffer(conf)
	dispatcher := NewCalculationGraph(eventBuffer, conf.FelixHostname)
	g := &AsyncCalcGraph{
		inputEvents:  make(chan interface{}, 10),
		outputEvents: outputEvents,
		Dispatcher:   dispatcher,
		eventBuffer:  eventBuffer,
	}
	eventBuffer.Callback = g.onEvent
	return g
}

func (acg *AsyncCalcGraph) OnUpdates(updates []model.Update) {
	log.Debugf("Got %v updates; queueing", len(updates))
	acg.inputEvents <- updates
}

func (acg *AsyncCalcGraph) OnStatusUpdated(status api.SyncStatus) {
	log.Debugf("Status updated: %v; queueing", status)
	acg.inputEvents <- status
	dataplaneStatusGauge.Set(statusToGaugeValue[status])
	if status == api.ResyncInProgress {
		resyncsStarted.Inc()
	}
}

func (acg *AsyncCalcGraph) loop() {
	log.Info("AsyncCalcGraph running")
	for {
		select {
		case update := <-acg.inputEvents:
			switch update := update.(type) {
			case []model.Update:
				// Update; send it to the dispatcher.
				log.Debug("Pulled []KVPair off channel")
				acg.Dispatcher.OnUpdates(update)
			case api.SyncStatus:
				// Sync status changed, check if we're now in-sync.
				log.WithField("status", update).Debug(
					"Pulled status update off channel")
				acg.Dispatcher.OnStatusUpdated(update)
				if update == api.InSync && !acg.beenInSync {
					log.Info("First time we've been in sync")
					acg.beenInSync = true
					acg.needToSendInSync = true
					acg.dirty = true
					if acg.flushLeakyBucket == 0 {
						// Force a flush.
						acg.flushLeakyBucket++
					}
				}
			default:
				log.Fatalf("Unexpected update: %#v", update)
			}
			acg.dirty = true
		case <-acg.flushTicks:
			// Timer tick: fill up the leaky bucket.
			if acg.flushLeakyBucket < leakyBucketSize {
				acg.flushLeakyBucket++
			}
		}
		acg.maybeFlush()
	}
}

// maybeFlush flushes the event buffer if: we know it's dirty and we're not throttled.
func (acg *AsyncCalcGraph) maybeFlush() {
	if !acg.dirty {
		return
	}
	if acg.flushLeakyBucket > 0 {
		log.Debug("Not throttled: flushing event buffer")
		acg.flushLeakyBucket--
		acg.eventBuffer.Flush()
		if acg.needToSendInSync {
			log.Info("First flush after becoming in sync, sending InSync message.")
			acg.onEvent(&proto.InSync{})
			acg.needToSendInSync = false
		}
		acg.dirty = false
	} else {
		log.Debug("Throttled: not flushing event buffer")
	}
}

func (acg *AsyncCalcGraph) onEvent(event interface{}) {
	log.Debug("Sending output event on channel")
	acg.outputEvents <- event
	log.Debug("Sent output event on channel")
}

func (acg *AsyncCalcGraph) Start() {
	log.Info("Starting AsyncCalcGraph")
	acg.flushTicks = time.Tick(tickInterval)
	go acg.loop()
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
)

var (
	ErrMessageAlreadyInTheChannel = errors.New("message is already in the channel")
	ErrIdIsNotSet                 = errors.New("Id is not set")
	ErrAccountIdIsNotSet          = errors.New("account id is not set")
	ErrOldIdIsNotSet              = errors.New("old id is not set")
	ErrNickIsNotSet               = errors.New("nick is not set")
	ErrGuestsAreNotAllowed        = errors.New("guests are not allowed")

	ErrMessageIdIsNotSet       = errors.New("message id is not set")
	ErrMessageIsNotSet         = errors.New("message is not set")
	ErrParentMessageIsNotSet   = errors.New("parent message is not set")
	ErrParentMessageIdIsNotSet = errors.New("parent message id is not set")
	ErrCreatorIdIsNotSet       = errors.New("creator id is not set")
	ErrSystemTypeIsNotSet      = errors.New("systemType is not set in payload")

	ErrChannelIsNotSet                = errors.New("channel is not set")
	ErrChannelIdIsNotSet              = errors.New("channel id is not set")
	ErrChannelContainerIsNotSet       = errors.New("channel container is not set")
	ErCouldntFindAccountIdFromContent = errors.New("couldnt find account id from content")
	ErrAccountIsAlreadyInTheChannel   = errors.New("account is already in the channel")

	ErrChannelParticipantIsNotSet             = errors.New("channel participant is not set")
	ErrCannotAddNewParticipantToPinnedChannel = errors.New("you can not add any participants to pinned activity channel")

	ErrChannelMessageIdIsNotSet        = errors.New("channel message id is not set")
	ErrChannelMessageUpdatedNotAllowed = errors.New("join/leave message update is not allowed")

	ErrNameIsNotSet       = errors.New("name is not set")
	ErrGroupNameIsNotSet  = errors.New("group name is not set")
	ErrGroupNotFound      = errors.New("group is not found")
	ErrLastSeenAtIsNotSet = errors.New("lastSeenAt is not set")
	ErrAddedAtIsNotSet    = errors.New("addedAt is not set")

	ErrRecipientsNotDefined = errors.New("recipients are not defined")
	ErrCannotOpenChannel    = errors.New("you can not open the channel")
	ErrSlugIsNotSet         = errors.New("slug is not set")

	ErrChannelOrMessageIdIsNotSet = errors.New("channelId/messageId is not set")

	ErrNotLoggedIn = errors.New("not logged in")

	ErrAccessDenied = errors.New("access denied")

	ErrRoleNotSet          = errors.New("role not set")
	ErrAccountNotFound     = errors.New("account not found")
	ErrChannelNotFound     = errors.New("channel not found")
	ErrParticipantNotFound = errors.New("participant not found")
	ErrParticipantBlocked  = errors.New("participant is blocked")

	// moderation
	ErrLeafIsNotSet          = errors.New("leaf channel is not set")
	ErrRootIsNotSet          = errors.New("root channel is not set")
	ErrChannelHasLeaves      = errors.New("channel has leaves")
	ErrChannelIsLinked       = errors.New("channel is linked")
	ErrGroupsAreNotSame      = errors.New("groups are not same")
	ErrLeafIsRootToo         = errors.New("leaf channel is root of another channel")
	ErrLinkingProcessNotDone = errors.New("channel linking process is not finished")

	ErrLoggerNotExist = errors.New("logger does not exist")
	ErrRedisNotExist  = errors.New("redis connection is not established")
)

type ChannelIsLeafError error

func ErrChannelIsLeafFunc(rootName, typeConstant string) error {
	return ChannelIsLeafError(
		fmt.Errorf(
			// poor man's json encoding - not to handle error case of
			// json.MarshalJSON, if we add new properties into this string, we
			// should use std package
			"{\"rootName\":\"%s\", \"typeConstant\":\"%s\"}",
			rootName,
			typeConstant,
		))
}

func IsChannelLeafErr(err error) bool {
	if err == nil {
		return false
	}

	_, ok := err.(ChannelIsLeafError)
	return ok
}
<commit_msg>socialapi: error is added for account is not participant<commit_after>package models

import (
	"errors"
	"fmt"
)

var (
	ErrMessageAlreadyInTheChannel = errors.New("message is already in the channel")
	ErrIdIsNotSet                 = errors.New("Id is not set")
	ErrAccountIdIsNotSet          = errors.New("account id is not set")
	ErrOldIdIsNotSet              = errors.New("old id is not set")
	ErrNickIsNotSet               = errors.New("nick is not set")
	ErrGuestsAreNotAllowed        = errors.New("guests are not allowed")

	ErrMessageIdIsNotSet       = errors.New("message id is not set")
	ErrMessageIsNotSet         = errors.New("message is not set")
	ErrParentMessageIsNotSet   = errors.New("parent message is not set")
	ErrParentMessageIdIsNotSet = errors.New("parent message id is not set")
	ErrCreatorIdIsNotSet       = errors.New("creator id is not set")
	ErrSystemTypeIsNotSet      = errors.New("systemType is not set in payload")

	ErrChannelIsNotSet                = errors.New("channel is not set")
	ErrChannelIdIsNotSet              = errors.New("channel id is not set")
	ErrChannelContainerIsNotSet       = errors.New("channel container is not set")
	ErCouldntFindAccountIdFromContent = errors.New("couldnt find account id from content")
	ErrAccountIsAlreadyInTheChannel   = errors.New("account is already in the channel")

	ErrChannelParticipantIsNotSet             = errors.New("channel participant is not set")
	ErrCannotAddNewParticipantToPinnedChannel = errors.New("you can not add any participants to pinned activity channel")

	ErrChannelMessageIdIsNotSet        = errors.New("channel message id is not set")
	ErrChannelMessageUpdatedNotAllowed = errors.New("join/leave message update is not allowed")

	ErrNameIsNotSet       = errors.New("name is not set")
	ErrGroupNameIsNotSet  = errors.New("group name is not set")
	ErrGroupNotFound      = errors.New("group is not found")
	ErrLastSeenAtIsNotSet = errors.New("lastSeenAt is not set")
	ErrAddedAtIsNotSet    = errors.New("addedAt is not set")

	ErrRecipientsNotDefined = errors.New("recipients are not defined")
	ErrCannotOpenChannel    = errors.New("you can not open the channel")
	ErrSlugIsNotSet         = errors.New("slug is not set")

	ErrChannelOrMessageIdIsNotSet = errors.New("channelId/messageId is not set")

	ErrNotLoggedIn = errors.New("not logged in")

	ErrAccessDenied = errors.New("access denied")

	ErrRoleNotSet              = errors.New("role not set")
	ErrAccountNotFound         = errors.New("account not found")
	ErrChannelNotFound         = errors.New("channel not found")
	ErrParticipantNotFound     = errors.New("participant not found")
	ErrParticipantBlocked      = errors.New("participant is blocked")
	ErrAccountIsNotParticipant = errors.New("account is not participant of channel")

	// moderation
	ErrLeafIsNotSet          = errors.New("leaf channel is not set")
	ErrRootIsNotSet          = errors.New("root channel is not set")
	ErrChannelHasLeaves      = errors.New("channel has leaves")
	ErrChannelIsLinked       = errors.New("channel is linked")
	ErrGroupsAreNotSame      = errors.New("groups are not same")
	ErrLeafIsRootToo         = errors.New("leaf channel is root of another channel")
	ErrLinkingProcessNotDone = errors.New("channel linking process is not finished")

	ErrLoggerNotExist = errors.New("logger does not exist")
	ErrRedisNotExist  = errors.New("redis connection is not established")
)

type ChannelIsLeafError error

func ErrChannelIsLeafFunc(rootName, typeConstant string) error {
	return ChannelIsLeafError(
		fmt.Errorf(
			// poor man's json encoding - not to handle error case of
			// json.MarshalJSON, if we add new properties into this string, we
			// should use std package
			"{\"rootName\":\"%s\", \"typeConstant\":\"%s\"}",
			rootName,
			typeConstant,
		))
}

func IsChannelLeafErr(err error) bool {
	if err == nil {
		return false
	}

	_, ok := err.(ChannelIsLeafError)
	return ok
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google Inc.

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 agreedto 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 consultopo

import (
	"path"

	"golang.org/x/net/context"

	"github.com/hashicorp/consul/api"
	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/topo"
)

// NewMasterParticipation is part of the topo.Server interface
func (s *Server) NewMasterParticipation(name, id string) (topo.MasterParticipation, error) {
	return &consulMasterParticipation{
		s:    s,
		name: name,
		id:   id,
		stop: make(chan struct{}),
		done: make(chan struct{}),
	}, nil
}

// consulMasterParticipation implements topo.MasterParticipation.
//
// We use a key with name <global>/elections/<name> for the lock,
// that contains the id.
type consulMasterParticipation struct {
	// s is our parent consul topo Server
	s *Server

	// name is the name of this MasterParticipation
	name string

	// id is the process's current id.
	id string

	// stop is a channel closed when Stop is called.
	stop chan struct{}

	// done is a channel closed when we're done processing the Stop
	done chan struct{}
}

// WaitForMastership is part of the topo.MasterParticipation interface.
func (mp *consulMasterParticipation) WaitForMastership() (context.Context, error) {

	electionPath := path.Join(mp.s.root, electionsPath, mp.name)
	l, err := mp.s.client.LockOpts(&api.LockOptions{
		Key:   electionPath,
		Value: []byte(mp.id),
	})
	if err != nil {
		return nil, err
	}

	// If Stop was already called, mp.done is closed, so we are interrupted.
	select {
	case <-mp.done:
		return nil, topo.NewError(topo.Interrupted, "mastership")
	default:
	}

	// Try to lock until mp.stop is closed.
	lost, err := l.Lock(mp.stop)
	if err != nil {
		// We can't lock. See if it was because we got canceled.
		select {
		case <-mp.stop:
			close(mp.done)
		default:
		}
		return nil, err
	}

	// We have the lock, keep mastership until we loose it.
	lockCtx, lockCancel := context.WithCancel(context.Background())
	go func() {
		select {
		case <-lost:
			// We lost the lock, nothing to do but lockCancel().
			lockCancel()
		case <-mp.stop:
			// Stop was called. We stop the context first,
			// so the running process is not thinking it
			// is the master any more, then we unlock.
			lockCancel()
			if err := l.Unlock(); err != nil {
				log.Errorf("master election(%v) Unlock failed: %v", mp.name, err)
			}
			close(mp.done)
		}
	}()

	return lockCtx, nil
}

// Stop is part of the topo.MasterParticipation interface
func (mp *consulMasterParticipation) Stop() {
	close(mp.stop)
	<-mp.done
}

// GetCurrentMasterID is part of the topo.MasterParticipation interface
func (mp *consulMasterParticipation) GetCurrentMasterID(ctx context.Context) (string, error) {
	electionPath := path.Join(mp.s.root, electionsPath, mp.name)
	pair, _, err := mp.s.kv.Get(electionPath, nil)
	if err != nil {
		return "", err
	}
	if pair == nil {
		return "", nil
	}
	return string(pair.Value), nil
}
<commit_msg>Calls Unlock explicitly after potetiantly losing it<commit_after>/*
Copyright 2017 Google Inc.

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 agreedto 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 consultopo

import (
	"path"

	"golang.org/x/net/context"

	"github.com/hashicorp/consul/api"
	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/topo"
)

// NewMasterParticipation is part of the topo.Server interface
func (s *Server) NewMasterParticipation(name, id string) (topo.MasterParticipation, error) {
	return &consulMasterParticipation{
		s:    s,
		name: name,
		id:   id,
		stop: make(chan struct{}),
		done: make(chan struct{}),
	}, nil
}

// consulMasterParticipation implements topo.MasterParticipation.
//
// We use a key with name <global>/elections/<name> for the lock,
// that contains the id.
type consulMasterParticipation struct {
	// s is our parent consul topo Server
	s *Server

	// name is the name of this MasterParticipation
	name string

	// id is the process's current id.
	id string

	// stop is a channel closed when Stop is called.
	stop chan struct{}

	// done is a channel closed when we're done processing the Stop
	done chan struct{}
}

// WaitForMastership is part of the topo.MasterParticipation interface.
func (mp *consulMasterParticipation) WaitForMastership() (context.Context, error) {

	electionPath := path.Join(mp.s.root, electionsPath, mp.name)
	l, err := mp.s.client.LockOpts(&api.LockOptions{
		Key:   electionPath,
		Value: []byte(mp.id),
	})
	if err != nil {
		return nil, err
	}

	// If Stop was already called, mp.done is closed, so we are interrupted.
	select {
	case <-mp.done:
		return nil, topo.NewError(topo.Interrupted, "mastership")
	default:
	}

	// Try to lock until mp.stop is closed.
	lost, err := l.Lock(mp.stop)
	if err != nil {
		// We can't lock. See if it was because we got canceled.
		select {
		case <-mp.stop:
			close(mp.done)
		default:
		}
		return nil, err
	}

	// We have the lock, keep mastership until we loose it.
	lockCtx, lockCancel := context.WithCancel(context.Background())
	go func() {
		select {
		case <-lost:
			lockCancel()
			// We could have lost the lock. Per consul API, explicitly call Unlock to make sure that session will not be renewed.
			if err := l.Unlock(); err != nil {
				log.Errorf("master election(%v) Unlock failed: %v", mp.name, err)
			}
		case <-mp.stop:
			// Stop was called. We stop the context first,
			// so the running process is not thinking it
			// is the master any more, then we unlock.
			lockCancel()
			if err := l.Unlock(); err != nil {
				log.Errorf("master election(%v) Unlock failed: %v", mp.name, err)
			}
			close(mp.done)
		}
	}()

	return lockCtx, nil
}

// Stop is part of the topo.MasterParticipation interface
func (mp *consulMasterParticipation) Stop() {
	close(mp.stop)
	<-mp.done
}

// GetCurrentMasterID is part of the topo.MasterParticipation interface
func (mp *consulMasterParticipation) GetCurrentMasterID(ctx context.Context) (string, error) {
	electionPath := path.Join(mp.s.root, electionsPath, mp.name)
	pair, _, err := mp.s.kv.Get(electionPath, nil)
	if err != nil {
		return "", err
	}
	if pair == nil {
		return "", nil
	}
	return string(pair.Value), nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"net"
)

type Client struct {
	Name            string   //Name of the miner
	IP              string   //Ip to the cgminer including port
	Conn            net.Conn //Connection made with net.Dial
	RefreshInterval int      //Seconds between fetching information
}

//Main function for fetching information from one client
func rpcClient(name, ip string, refInt int) {
	//Add everything except the connection
	c := Client{name, ip, nil, refInt}

	fmt.Println(c)

	//Create the connection
	conn, err := net.Dial("tcp", c.IP)

	//Check for errors
	if err != nil {
		panic(err)
	}

	//Save it because it went well
	c.Conn = conn
	//Send test command
	fmt.Fprintf(conn, "{\"command\":\"version\"}")
	//Read the response
	status, err := bufio.NewReader(conn).ReadString('\n')
	//Print it
	fmt.Println(status)
}
<commit_msg>Extracted the read and write to the socket to own function<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
)

type Client struct {
	Name            string   //Name of the miner
	IP              string   //Ip to the cgminer including port
	Conn            net.Conn //Connection made with net.Dial
	RefreshInterval int      //Seconds between fetching information
}

//Main function for fetching information from one client
func rpcClient(name, ip string, refInt int) {
	//Add everything except the connection
	c := Client{name, ip, nil, refInt}

	fmt.Println(c)

	//Create the connection
	conn, err := net.Dial("tcp", c.IP)

	//Check for errors
	if err != nil {
		log.Println(err)
		return
	}

	//Save it because it went well
	c.Conn = conn
	//Send test command
	fmt.Println(sendCommand(&c.Conn, "{\"command\":\"version\"}"))
}

func sendCommand(conn *net.Conn, cmd string) string {
	//Write the command to the socket
	fmt.Fprintf(*conn, cmd)
	//Read the response
	status, err := bufio.NewReader(*conn).ReadString('\n')
	//Check for any errors
	if err != nil {
		//If the error is not EOF then warn about it
		if err != io.EOF {
			log.Println("Sending command error: ", err)
		}
	}
	//Return the status we got from the server
	return status
}
<|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. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package rpc

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/rpc"
	"strings"
	"sync"

	"github.com/cockroachdb/cockroach/rpc/codec"
	"github.com/cockroachdb/cockroach/security"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
	"github.com/gogo/protobuf/proto"
)

// Server is a Cockroach-specific RPC server with an embedded go RPC
// server struct. By default it handles a simple heartbeat protocol
// to measure link health. It also supports close callbacks.
//
// TODO(spencer): heartbeat protocol should also measure link latency.
type Server struct {
	*rpc.Server              // Embedded RPC server instance
	listener    net.Listener // Server listener

	activeConns map[net.Conn]struct{}
	handler     http.Handler

	context *Context

	mu             sync.RWMutex          // Mutex protects the fields below
	addr           net.Addr              // Server address; may change if picking unused port
	closed         bool                  // Set upon invocation of Close()
	closeCallbacks []func(conn net.Conn) // Slice of callbacks to invoke on conn close
}

// NewServer creates a new instance of Server.
func NewServer(addr net.Addr, context *Context) *Server {
	s := &Server{
		Server:  rpc.NewServer(),
		context: context,
		addr:    addr,
	}
	heartbeat := &HeartbeatService{
		clock:              context.localClock,
		remoteClockMonitor: context.RemoteClocks,
	}
	if err := s.RegisterName("Heartbeat", heartbeat); err != nil {
		log.Fatalf("unable to register heartbeat service with RPC server: %s", err)
	}
	return s
}

// AddCloseCallback adds a callback to the closeCallbacks slice to
// be invoked when a connection is closed.
func (s *Server) AddCloseCallback(cb func(conn net.Conn)) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.closeCallbacks = append(s.closeCallbacks, cb)
}

// Can connect to RPC service using HTTP CONNECT to rpcPath.
var connected = "200 Connected to Go RPC"

// ServeHTTP implements an http.Handler that answers RPC requests.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != rpc.DefaultRPCPath {
		if s.handler != nil {
			s.handler.ServeHTTP(w, r)
			return
		}
		http.NotFound(w, r)
		return
	}

	// Note: this code was adapted from net/rpc.Server.ServeHTTP.
	if r.Method != "CONNECT" {
		http.Error(w, "405 must CONNECT", http.StatusMethodNotAllowed)
		return
	}

	// Construct an authentication hook for this security mode and TLS state.
	authHook, err := security.AuthenticationHook(s.context.Insecure, r.TLS)
	if err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	}

	conn, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		log.Infof("rpc hijacking %s: %s", r.RemoteAddr, err)
		return
	}
	security.LogTLSState("RPC", r.TLS)
	io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
	s.serveConn(conn, authHook)
}

// Listen listens on the configured address but does not start
// accepting connections until Serve is called.
func (s *Server) Listen() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	tlsConfig, err := s.context.GetServerTLSConfig()
	if err != nil {
		return err
	}
	ln, err := tlsListen(s.addr.Network(), s.addr.String(), tlsConfig)
	if err != nil {
		return err
	}
	s.listener = ln

	addr, err := updatedAddr(s.addr, ln.Addr())
	if err != nil {
		s.Close()
		return err
	}
	s.addr = addr

	return nil
}

// Serve accepts and services connections on the already started
// listener.
func (s *Server) Serve(handler http.Handler) {
	s.handler = handler
	s.activeConns = make(map[net.Conn]struct{})

	server := &http.Server{
		Handler: s,
		ConnState: func(conn net.Conn, state http.ConnState) {
			s.mu.Lock()
			defer s.mu.Unlock()

			switch state {
			case http.StateNew, http.StateHijacked:
				if s.closed {
					conn.Close()
					return
				}
				s.activeConns[conn] = struct{}{}
			case http.StateClosed:
				delete(s.activeConns, conn)
			}
		},
	}

	s.context.Stopper.RunWorker(func() {
		if err := server.Serve(s.listener); err != nil {
			if !strings.HasSuffix(err.Error(), "use of closed network connection") {
				log.Fatal(err)
			}
		}
	})

	s.context.Stopper.RunWorker(func() {
		<-s.context.Stopper.ShouldStop()
		s.Close()
	})
}

// Start runs the RPC server. After this method returns, the socket
// will have been bound. Use Server.Addr() to ascertain server address.
func (s *Server) Start() error {
	if err := s.Listen(); err != nil {
		return err
	}
	s.Serve(s)
	return nil
}

// updatedAddr returns our "official" address based on the address we asked for
// (oldAddr) and the address we successfully bound to (newAddr). It's kind of
// hacky, but necessary to make TLS work.
func updatedAddr(oldAddr, newAddr net.Addr) (net.Addr, error) {
	switch oldAddr.Network() {
	case "tcp", "tcp4", "tcp6":
		// After binding, it's possible that our host and/or port will be
		// different from what we requested. If the hostname is different, we
		// want to keep the original one since it's more likely to match our
		// TLS certificate. But if the port is different, it should be because
		// we asked for ":0" and got an arbitrary unused port; that needs to be
		// reflected in our addr.
		host, oldPort, err := net.SplitHostPort(util.EnsureHost(oldAddr.String()))
		if err != nil {
			return nil, fmt.Errorf("unable to parse original addr '%s': %v",
				oldAddr.String(), err)
		}
		_, newPort, err := net.SplitHostPort(newAddr.String())
		if err != nil {
			return nil, fmt.Errorf("unable to parse new addr '%s': %v",
				newAddr.String(), err)
		}

		if newPort != oldPort && oldPort != "0" {
			log.Warningf("asked for port %s, got %s", oldPort, newPort)
		}

		return util.MakeUnresolvedAddr("tcp", net.JoinHostPort(host, newPort)), nil

	case "unix":
		if oldAddr.String() != newAddr.String() {
			return nil, fmt.Errorf("asked for unix addr %s, got %s", oldAddr, newAddr)
		}
		return newAddr, nil

	default:
		return nil, fmt.Errorf("unexpected network type: %s", oldAddr.Network())
	}
}

// Addr returns the server's network address.
func (s *Server) Addr() net.Addr {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.addr
}

// Close closes the listener.
func (s *Server) Close() {
	// If the server didn't start properly, it might not have a listener.
	if s.listener != nil {
		s.listener.Close()
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.closed = true

	for conn := range s.activeConns {
		conn.Close()
	}
}

// serveConn synchronously serves a single connection. When the
// connection is closed, close callbacks are invoked.
func (s *Server) serveConn(conn net.Conn, authHook func(proto.Message) error) {
	s.ServeCodec(codec.NewServerCodec(conn, authHook))
	s.mu.Lock()
	if s.closeCallbacks != nil {
		for _, cb := range s.closeCallbacks {
			cb(conn)
		}
	}
	s.mu.Unlock()
	conn.Close()
}
<commit_msg>Remove redundant case match<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. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package rpc

import (
	"fmt"
	"io"
	"net"
	"net/http"
	"net/rpc"
	"strings"
	"sync"

	"github.com/cockroachdb/cockroach/rpc/codec"
	"github.com/cockroachdb/cockroach/security"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
	"github.com/gogo/protobuf/proto"
)

// Server is a Cockroach-specific RPC server with an embedded go RPC
// server struct. By default it handles a simple heartbeat protocol
// to measure link health. It also supports close callbacks.
//
// TODO(spencer): heartbeat protocol should also measure link latency.
type Server struct {
	*rpc.Server              // Embedded RPC server instance
	listener    net.Listener // Server listener

	activeConns map[net.Conn]struct{}
	handler     http.Handler

	context *Context

	mu             sync.RWMutex          // Mutex protects the fields below
	addr           net.Addr              // Server address; may change if picking unused port
	closed         bool                  // Set upon invocation of Close()
	closeCallbacks []func(conn net.Conn) // Slice of callbacks to invoke on conn close
}

// NewServer creates a new instance of Server.
func NewServer(addr net.Addr, context *Context) *Server {
	s := &Server{
		Server:  rpc.NewServer(),
		context: context,
		addr:    addr,
	}
	heartbeat := &HeartbeatService{
		clock:              context.localClock,
		remoteClockMonitor: context.RemoteClocks,
	}
	if err := s.RegisterName("Heartbeat", heartbeat); err != nil {
		log.Fatalf("unable to register heartbeat service with RPC server: %s", err)
	}
	return s
}

// AddCloseCallback adds a callback to the closeCallbacks slice to
// be invoked when a connection is closed.
func (s *Server) AddCloseCallback(cb func(conn net.Conn)) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.closeCallbacks = append(s.closeCallbacks, cb)
}

// Can connect to RPC service using HTTP CONNECT to rpcPath.
var connected = "200 Connected to Go RPC"

// ServeHTTP implements an http.Handler that answers RPC requests.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != rpc.DefaultRPCPath {
		if s.handler != nil {
			s.handler.ServeHTTP(w, r)
			return
		}
		http.NotFound(w, r)
		return
	}

	// Note: this code was adapted from net/rpc.Server.ServeHTTP.
	if r.Method != "CONNECT" {
		http.Error(w, "405 must CONNECT", http.StatusMethodNotAllowed)
		return
	}

	// Construct an authentication hook for this security mode and TLS state.
	authHook, err := security.AuthenticationHook(s.context.Insecure, r.TLS)
	if err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	}

	conn, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		log.Infof("rpc hijacking %s: %s", r.RemoteAddr, err)
		return
	}
	security.LogTLSState("RPC", r.TLS)
	io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
	s.serveConn(conn, authHook)
}

// Listen listens on the configured address but does not start
// accepting connections until Serve is called.
func (s *Server) Listen() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	tlsConfig, err := s.context.GetServerTLSConfig()
	if err != nil {
		return err
	}
	ln, err := tlsListen(s.addr.Network(), s.addr.String(), tlsConfig)
	if err != nil {
		return err
	}
	s.listener = ln

	addr, err := updatedAddr(s.addr, ln.Addr())
	if err != nil {
		s.Close()
		return err
	}
	s.addr = addr

	return nil
}

// Serve accepts and services connections on the already started
// listener.
func (s *Server) Serve(handler http.Handler) {
	s.handler = handler
	s.activeConns = make(map[net.Conn]struct{})

	server := &http.Server{
		Handler: s,
		ConnState: func(conn net.Conn, state http.ConnState) {
			s.mu.Lock()
			defer s.mu.Unlock()

			switch state {
			case http.StateNew:
				if s.closed {
					conn.Close()
					return
				}
				s.activeConns[conn] = struct{}{}
			case http.StateClosed:
				delete(s.activeConns, conn)
			}
		},
	}

	s.context.Stopper.RunWorker(func() {
		if err := server.Serve(s.listener); err != nil {
			if !strings.HasSuffix(err.Error(), "use of closed network connection") {
				log.Fatal(err)
			}
		}
	})

	s.context.Stopper.RunWorker(func() {
		<-s.context.Stopper.ShouldStop()
		s.Close()
	})
}

// Start runs the RPC server. After this method returns, the socket
// will have been bound. Use Server.Addr() to ascertain server address.
func (s *Server) Start() error {
	if err := s.Listen(); err != nil {
		return err
	}
	s.Serve(s)
	return nil
}

// updatedAddr returns our "official" address based on the address we asked for
// (oldAddr) and the address we successfully bound to (newAddr). It's kind of
// hacky, but necessary to make TLS work.
func updatedAddr(oldAddr, newAddr net.Addr) (net.Addr, error) {
	switch oldAddr.Network() {
	case "tcp", "tcp4", "tcp6":
		// After binding, it's possible that our host and/or port will be
		// different from what we requested. If the hostname is different, we
		// want to keep the original one since it's more likely to match our
		// TLS certificate. But if the port is different, it should be because
		// we asked for ":0" and got an arbitrary unused port; that needs to be
		// reflected in our addr.
		host, oldPort, err := net.SplitHostPort(util.EnsureHost(oldAddr.String()))
		if err != nil {
			return nil, fmt.Errorf("unable to parse original addr '%s': %v",
				oldAddr.String(), err)
		}
		_, newPort, err := net.SplitHostPort(newAddr.String())
		if err != nil {
			return nil, fmt.Errorf("unable to parse new addr '%s': %v",
				newAddr.String(), err)
		}

		if newPort != oldPort && oldPort != "0" {
			log.Warningf("asked for port %s, got %s", oldPort, newPort)
		}

		return util.MakeUnresolvedAddr("tcp", net.JoinHostPort(host, newPort)), nil

	case "unix":
		if oldAddr.String() != newAddr.String() {
			return nil, fmt.Errorf("asked for unix addr %s, got %s", oldAddr, newAddr)
		}
		return newAddr, nil

	default:
		return nil, fmt.Errorf("unexpected network type: %s", oldAddr.Network())
	}
}

// Addr returns the server's network address.
func (s *Server) Addr() net.Addr {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.addr
}

// Close closes the listener.
func (s *Server) Close() {
	// If the server didn't start properly, it might not have a listener.
	if s.listener != nil {
		s.listener.Close()
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.closed = true

	for conn := range s.activeConns {
		conn.Close()
	}
}

// serveConn synchronously serves a single connection. When the
// connection is closed, close callbacks are invoked.
func (s *Server) serveConn(conn net.Conn, authHook func(proto.Message) error) {
	s.ServeCodec(codec.NewServerCodec(conn, authHook))
	s.mu.Lock()
	if s.closeCallbacks != nil {
		for _, cb := range s.closeCallbacks {
			cb(conn)
		}
	}
	s.mu.Unlock()
	conn.Close()
}
<|endoftext|>


<commit_before>// +build !gtk_3_6,!gtk_3_8,!gtk_3_10,!gtk_3_12,!gtk_3_14,!gtk_3_16,!gtk_3_18,!gtk_3_20

package gtk

// #include <gtk/gtk.h>
// #include "gtk.go.h"
// #include "shortcutswindow_since_3_22.go.h"
import "C"
import (
	"unsafe"

	"github.com/gotk3/gotk3/glib"
)

func init() {
	tm := []glib.TypeMarshaler{
		{glib.Type(C.gtk_shortcuts_window_get_type()), marshalShortcutsWindow},
		{glib.Type(C.gtk_shortcuts_section_get_type()), marshalShortcutsSection},
		{glib.Type(C.gtk_shortcuts_group_get_type()), marshalShortcutsGroup},
		{glib.Type(C.gtk_shortcuts_shortcut_get_type()), marshalShortcutsShortcut},
	}

	glib.RegisterGValueMarshalers(tm)

	WrapMap["GtkShortcutsWindow"] = wrapShortcutsWindow
	WrapMap["GtkShortcutsSection"] = wrapShortcutsSection
	WrapMap["GtkShortcutsGroup"] = wrapShortcutsGroup
	WrapMap["GtkShortcutsShortcut"] = wrapShortcutsShortcut
}

/*
 * GtkShortcutsWindow
 */

// ShortcutsWindow is a representation of GTK's GtkShortcutsWindow.
type ShortcutsWindow struct {
	Window
}

func (v *ShortcutsWindow) native() *C.GtkShortcutsWindow {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsWindow(p)
}

func marshalShortcutsWindow(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsWindow(obj), nil
}

func wrapShortcutsWindow(obj *glib.Object) *ShortcutsWindow {
	return &ShortcutsWindow{Window{Bin{Container{Widget{glib.InitiallyUnowned{obj}}}}}}
}

/*
 * GtkShortcutsSection
 */

// ShortcutsWindow is a representation of GTK's GtkShortcutsSection.
type ShortcutsSection struct {
	Box
}

// native returns a pointer to the underlying GtkShortcutsSection.
func (v *ShortcutsSection) native() *C.GtkShortcutsSection {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsSection(p)
}

func marshalShortcutsSection(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsSection(obj), nil
}

func wrapShortcutsSection(obj *glib.Object) *ShortcutsSection {
	return &ShortcutsSection{Box{Container{Widget{glib.InitiallyUnowned{obj}}}}}
}

/*
 * GtkShortcutsSection
 */

// ShortcutsWindow is a representation of GTK's GtkShortcutsGroup.
type ShortcutsGroup struct {
	Box
}

// native returns a pointer to the underlying GtkShortcutsGroup.
func (v *ShortcutsGroup) native() *C.GtkShortcutsGroup {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsGroup(p)
}

func marshalShortcutsGroup(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsGroup(obj), nil
}

func wrapShortcutsGroup(obj *glib.Object) *ShortcutsGroup {
	return &ShortcutsGroup{Box{Container{Widget{glib.InitiallyUnowned{obj}}}}}
}

/*
 * GtkShortcutsShortcut
 */

// ShortcutsWindow is a representation of GTK's GtkShortcutsShortcut.
type ShortcutsShortcut struct {
	Box
}

// native returns a pointer to the underlying GtkShortcutsShortcut.
func (v *ShortcutsShortcut) native() *C.GtkShortcutsShortcut {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsShortcut(p)
}

func marshalShortcutsShortcut(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsShortcut(obj), nil
}

func wrapShortcutsShortcut(obj *glib.Object) *ShortcutsShortcut {
	return &ShortcutsShortcut{Box{Container{Widget{glib.InitiallyUnowned{obj}}}}}
}
<commit_msg>Fix mismatched comments<commit_after>// +build !gtk_3_6,!gtk_3_8,!gtk_3_10,!gtk_3_12,!gtk_3_14,!gtk_3_16,!gtk_3_18,!gtk_3_20

package gtk

// #include <gtk/gtk.h>
// #include "gtk.go.h"
// #include "shortcutswindow_since_3_22.go.h"
import "C"
import (
	"unsafe"

	"github.com/gotk3/gotk3/glib"
)

func init() {
	tm := []glib.TypeMarshaler{
		{glib.Type(C.gtk_shortcuts_window_get_type()), marshalShortcutsWindow},
		{glib.Type(C.gtk_shortcuts_section_get_type()), marshalShortcutsSection},
		{glib.Type(C.gtk_shortcuts_group_get_type()), marshalShortcutsGroup},
		{glib.Type(C.gtk_shortcuts_shortcut_get_type()), marshalShortcutsShortcut},
	}

	glib.RegisterGValueMarshalers(tm)

	WrapMap["GtkShortcutsWindow"] = wrapShortcutsWindow
	WrapMap["GtkShortcutsSection"] = wrapShortcutsSection
	WrapMap["GtkShortcutsGroup"] = wrapShortcutsGroup
	WrapMap["GtkShortcutsShortcut"] = wrapShortcutsShortcut
}

/*
 * GtkShortcutsWindow
 */

// ShortcutsWindow is a representation of GTK's GtkShortcutsWindow.
type ShortcutsWindow struct {
	Window
}

func (v *ShortcutsWindow) native() *C.GtkShortcutsWindow {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsWindow(p)
}

func marshalShortcutsWindow(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsWindow(obj), nil
}

func wrapShortcutsWindow(obj *glib.Object) *ShortcutsWindow {
	return &ShortcutsWindow{Window{Bin{Container{Widget{glib.InitiallyUnowned{obj}}}}}}
}

/*
 * GtkShortcutsSection
 */

// ShortcutsSection is a representation of GTK's GtkShortcutsSection.
type ShortcutsSection struct {
	Box
}

// native returns a pointer to the underlying GtkShortcutsSection.
func (v *ShortcutsSection) native() *C.GtkShortcutsSection {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsSection(p)
}

func marshalShortcutsSection(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsSection(obj), nil
}

func wrapShortcutsSection(obj *glib.Object) *ShortcutsSection {
	return &ShortcutsSection{Box{Container{Widget{glib.InitiallyUnowned{obj}}}}}
}

/*
 * GtkShortcutsGroup
 */

// ShortcutsGroup is a representation of GTK's GtkShortcutsGroup.
type ShortcutsGroup struct {
	Box
}

// native returns a pointer to the underlying GtkShortcutsGroup.
func (v *ShortcutsGroup) native() *C.GtkShortcutsGroup {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsGroup(p)
}

func marshalShortcutsGroup(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsGroup(obj), nil
}

func wrapShortcutsGroup(obj *glib.Object) *ShortcutsGroup {
	return &ShortcutsGroup{Box{Container{Widget{glib.InitiallyUnowned{obj}}}}}
}

/*
 * GtkShortcutsShortcut
 */

// ShortcutsShortcut is a representation of GTK's GtkShortcutsShortcut.
type ShortcutsShortcut struct {
	Box
}

// native returns a pointer to the underlying GtkShortcutsShortcut.
func (v *ShortcutsShortcut) native() *C.GtkShortcutsShortcut {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGtkShortcutsShortcut(p)
}

func marshalShortcutsShortcut(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := glib.Take(unsafe.Pointer(c))
	return wrapShortcutsShortcut(obj), nil
}

func wrapShortcutsShortcut(obj *glib.Object) *ShortcutsShortcut {
	return &ShortcutsShortcut{Box{Container{Widget{glib.InitiallyUnowned{obj}}}}}
}
<|endoftext|>


<commit_before>package cmd

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/fatih/color"
	"github.com/stormforger/cli/api"
	"github.com/stormforger/cli/api/filefixture"
	"github.com/stormforger/cli/api/organisation"
	"github.com/stormforger/cli/api/testcase"
	"github.com/stormforger/cli/api/testrun"
)

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// FindFixtureByName fetches a FileFixture from a given Organisation.
func findFixtureByName(client api.Client, orga string, name string) *filefixture.FileFixture {
	success, result, err := client.ListFileFixture(orga)
	if err != nil {
		log.Fatalf("ListFileFixtures failed: %v", err)
	}

	if !success {
		fmt.Fprintln(os.Stderr, "Could not lookup data source!")
		fmt.Fprintln(os.Stderr, string(result))

		os.Exit(1)
	}

	fileFixtures, err := filefixture.Unmarshal(bytes.NewReader(result))
	if err != nil {
		log.Fatalf("Unmarshal failed: %v", err)
	}

	fileFixture := fileFixtures.FindByName(name)
	if fileFixture.ID == "" {
		log.Fatalf("Data source %s not found in organisation %s!", name, orga)
	}

	return &fileFixture
}

// findOrganisationByName fetches a FileFixture from a given Organisation.
func findOrganisationByName(client api.Client, name string) *organisation.Organisation {
	status, response, err := client.ListOrganisations()
	if err != nil {
		log.Fatal(err)
	}
	if !status {
		log.Fatal(string(response))
	}

	organisations, err := organisation.Unmarshal(bytes.NewReader(response))
	if err != nil {
		log.Fatal(err)
	}

	organisation := organisations.FindByNameOrUID(name)
	if organisation.ID == "" {
		log.Fatalf("Organisation %s not found!", name)
	}

	return &organisation
}

// readFromStdinOrReadFromArgument returns the filename and a filereader for fileArg.
// If fileArg matches "-", the defaultFileName and stdin is returned
func readFromStdinOrReadFromArgument(fileArg, defaultFileName string) (fileName string, reader io.Reader, err error) {
	if fileArg == "-" {
		fileName = defaultFileName
		reader = os.Stdin
	} else {
		fileName = filepath.Base(fileArg)
		reader, err = os.OpenFile(fileArg, os.O_RDONLY, 0755)
		if err != nil {
			return "", nil, err
		}
	}

	return fileName, reader, err
}

func readTestCaseFromStdinOrReadFromArgument(arg, defaultFileName string) (fileName string, reader io.Reader, err error) {
	fileName, testCaseFile, err := readFromStdinOrReadFromArgument(arg, defaultFileName)
	if err != nil {
		log.Fatal(err)
	}

	basePath := ""
	if arg != "-" {
		basePath = filepath.Dir(arg)
	} else {
		var err error
		basePath, err = os.Getwd()
		if err != nil {
			log.Fatal(err)
		}
	}

	inputWithIncludes := processIncludes(testCaseFile, basePath)

	return fileName, inputWithIncludes, err
}

func processIncludes(input io.Reader, basePath string) io.Reader {
	pr, output := io.Pipe()
	re := regexp.MustCompile("//#include (.+?)$")

	go func() {
		defer output.Close()

		scanner := bufio.NewScanner(input)
		for scanner.Scan() {
			line := scanner.Text()

			if match := re.FindStringSubmatch(line); match != nil {
				includeFile := strings.TrimSpace(match[1])

				if !filepath.IsAbs(includeFile) {
					includeFile = filepath.Join(basePath, includeFile)
				}

				f, err := os.Open(includeFile)
				if err != nil {
					log.Fatal(err)
				}

				fmt.Fprint(output, "// == start include ("+includeFile+")\n")
				io.Copy(output, f)
				fmt.Fprint(output, "// == end include ("+includeFile+")\n")
			} else {
				fmt.Fprintln(output, line)
			}
		}
	}()

	return pr
}

func watchTestRun(testRunUID string, maxWatchTime float64, outputFormat string) {
	client := NewClient()
	started := time.Now()
	first := true
	testStarted := false
	testEnded := false

	for true {
		runningSince := time.Since(started).Seconds()

		testRun, response, err := client.TestRunWatch(testRunUID)
		if err != nil {
			log.Fatal(err)
		}

		if first {
			first = false

			if testRunSuccess(&testRun) {
				if outputFormat != "json" {
					fmt.Printf("Test Run %s is finished.\n", testRun.ID)
				}

				return
			}

			if outputFormat == "json" {
				fmt.Println(response)
			} else {
				formattedStartedAt := "not yet"
				formattedEstimatedEnd := "n/a"

				if testRun.StartedAt != "" {
					formattedStartedAt = humanize.Time(parseTime(testRun.StartedAt))
					testStarted = true
				}
				if testRun.EstimatedEnd != "" {
					formattedEstimatedEnd = humanize.Time(parseTime(testRun.EstimatedEnd))
				}

				fmt.Printf("[status] Test Run: %s started %s (est. end %s)\n", testRun.ID, formattedStartedAt, formattedEstimatedEnd)
			}
		}

		if outputFormat == "json" {
			fmt.Println(response)
		} else {
			switch testRun.State {
			case "running":
				if !testStarted {
					formattedStartedAt := "not yet"
					formattedEstimatedEnd := "n/a"

					if testRun.StartedAt != "" {
						formattedStartedAt = humanize.Time(parseTime(testRun.StartedAt))
					}
					if testRun.EstimatedEnd != "" {
						formattedEstimatedEnd = humanize.Time(parseTime(testRun.EstimatedEnd))
					}

					fmt.Printf("[status] Test Run: %s started %s (est. end %s)\n", testRun.ID, formattedStartedAt, formattedEstimatedEnd)

					testStarted = true
				}
				fmt.Printf("[%s] Progress: %d%%\n", testRun.State, testRun.Progress)
			default:
				if testStarted && !testEnded {
					fmt.Printf("[status] Test run ended...\n")
					testEnded = true
				}

				fmt.Printf("[%s]\n", testRun.State)
			}

		}

		if !testRunOkay(&testRun) {
			os.Exit(1)
		}

		if testRunSuccess(&testRun) {
			return
		}

		if int(maxWatchTime) > 0 && int(runningSince) > int(maxWatchTime) {
			os.Exit(2)
		}

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

func lookupOrganisationUID(client api.Client, input string) string {
	organisation := findOrganisationByName(client, input)
	if organisation.ID == "" {
		log.Fatalf("Organisation %s not found", input)
	}

	return organisation.ID
}

func lookupTestCase(client api.Client, input string) string {
	segments := strings.Split(input, "/")
	nameOrUID := input

	if len(segments) == 2 {
		organisationNameOrUID := segments[0]
		nameOrUID = segments[1]

		organisationUID := lookupOrganisationUID(client, organisationNameOrUID)

		_, result, err := client.ListTestCases(organisationUID, "all")
		if err != nil {
			log.Fatal(err)
		}

		testCases, err := testcase.Unmarshal(bytes.NewReader(result))
		if err != nil {
			log.Fatal(err)
		}

		testCase := testCases.FindByNameOrUID(nameOrUID)
		if testCase.ID == "" {
			log.Fatalf("Test case %s not found", nameOrUID)
		}

		return testCase.ID
	}

	return nameOrUID
}

func getTestRunUID(client api.Client, input string) string {
	testRunParts := api.ExtractTestRunResources(input)

	if testRunParts.UID != "" {
		return testRunParts.UID
	} else if testRunParts.Organisation == "" || testRunParts.TestCase == "" {
		log.Fatal("Invalid test run reference provided! Consult with --help to learn more.")
	}

	result := fetchTestRun(client, input)
	testRun, err := testrun.UnmarshalSingle(bytes.NewReader(result))
	if err != nil {
		log.Fatal(err)
	}

	return testRun.ID
}

func fetchTestRun(client api.Client, input string) []byte {
	testRunParts := api.ExtractTestRunResources(input)

	if testRunParts.UID != "" {
		status, response, err := client.FetchTestRun(testRunParts.UID)
		if err != nil {
			log.Fatal(err)
		}
		if !status {
			log.Fatalf("Could not load test run %s", testRunParts.UID)
		}

		return response
	} else if testRunParts.Organisation == "" || testRunParts.TestCase == "" {
		log.Fatal("Invalid test run reference provided! Consult with --help to learn more.")
	}

	status, response, err := client.LookupAndFetchResource("test_run", input)
	if err != nil {
		log.Fatal(err)
	}
	if !status {
		log.Fatalf("Test Run %s not found", input)
	}

	return response
}

func parseTime(subject string) time.Time {
	parsedTime, err := time.Parse(time.RFC3339Nano, subject)
	if err != nil {
		log.Fatal(err)
	}

	return parsedTime
}

func convertToLocalTZ(timeToConvert time.Time) time.Time {
	loc, err := time.LoadLocation("Local")
	if err != nil {
		log.Fatal(err)
	}

	return timeToConvert.In(loc)
}

func runNfrCheck(client api.Client, testRunUID string, fileName string, nfrData io.Reader) {
	status, result, err := client.TestRunNfrCheck(testRunUID, fileName, nfrData)
	if err != nil {
		log.Fatal(err)
	}

	if rootOpts.OutputFormat == "json" {
		fmt.Println(string(result))
		return
	}

	if !status {
		var response struct {
			Status  string
			Message string
			Error   string
		}

		log.Println("Could not perform test-run NFR checks...")
		if err := json.Unmarshal(result, &response); err != nil {
			log.Println(string(result))
		} else {
			if response.Status != "" {
				log.Printf(" Status:\t%s\n", response.Status)
			}
			if response.Message != "" {
				log.Printf(" Message:\t%s\n", response.Message)
			}
			if response.Error != "" {
				log.Printf(" Error:\t%s\n", response.Error)
			}
		}
		os.Exit(1)
	}

	items, err := testrun.UnmarshalNfrResults(bytes.NewReader(result))
	if err != nil {
		log.Fatal(err)
	}

	anyFails := displayNfrResult(items)

	if anyFails {
		os.Exit(1)
	}
}

func displayNfrResult(items testrun.NfrResultList) bool {
	green := color.New(color.FgGreen).SprintFunc()
	red := color.New(color.FgRed).SprintFunc()
	redBg := color.New(color.BgRed).Add(color.FgWhite).SprintFunc()
	white := color.New(color.FgWhite).SprintFunc()
	whiteBold := color.New(color.FgWhite, color.Bold).SprintFunc()

	checkStatus := ""
	anyFails := false
	for _, item := range items.NfrResults {
		if !item.Disabled {
			actualSubject := ""

			if item.SubjectAvailable {
				if item.Success {
					checkStatus = green("\u2713")
					actualSubject = fmt.Sprintf("was %s", item.SubjectWithUnit())
				} else {
					anyFails = true
					checkStatus = red("\u2717")
					actualSubject = fmt.Sprintf("but actually was %s", item.SubjectWithUnit())
				}
			} else {
				checkStatus = whiteBold("?")
				actualSubject = fmt.Sprintf("was %s", whiteBold("not available"))
			}

			filter := ""
			if item.Filter != "null" && item.Filter != "" {
				filter = " (where: " + item.Filter + ")"
			}

			fmt.Printf(
				"%s %s expected to be %s; %s (%s)%s\n",
				checkStatus,
				item.Metric,
				item.ExpectationWithUnit(),
				actualSubject,
				item.Type,
				filter,
			)
		} else {
			fmt.Printf(
				"%s %s %s expected to be %s (%s)\n",
				white("?"),
				redBg("DISABLED"),
				item.Metric,
				item.ExpectationWithUnit(),
				item.Type,
			)
		}
	}

	if !anyFails {
		fmt.Printf(green("\nAll checks passed!\n"))
	} else {
		fmt.Printf(red("\nYou have failing checks!\n"))
	}

	return anyFails
}
<commit_msg>feat: print nfr summary line (fixes #131)<commit_after>package cmd

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/fatih/color"
	"github.com/stormforger/cli/api"
	"github.com/stormforger/cli/api/filefixture"
	"github.com/stormforger/cli/api/organisation"
	"github.com/stormforger/cli/api/testcase"
	"github.com/stormforger/cli/api/testrun"
)

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// FindFixtureByName fetches a FileFixture from a given Organisation.
func findFixtureByName(client api.Client, orga string, name string) *filefixture.FileFixture {
	success, result, err := client.ListFileFixture(orga)
	if err != nil {
		log.Fatalf("ListFileFixtures failed: %v", err)
	}

	if !success {
		fmt.Fprintln(os.Stderr, "Could not lookup data source!")
		fmt.Fprintln(os.Stderr, string(result))

		os.Exit(1)
	}

	fileFixtures, err := filefixture.Unmarshal(bytes.NewReader(result))
	if err != nil {
		log.Fatalf("Unmarshal failed: %v", err)
	}

	fileFixture := fileFixtures.FindByName(name)
	if fileFixture.ID == "" {
		log.Fatalf("Data source %s not found in organisation %s!", name, orga)
	}

	return &fileFixture
}

// findOrganisationByName fetches a FileFixture from a given Organisation.
func findOrganisationByName(client api.Client, name string) *organisation.Organisation {
	status, response, err := client.ListOrganisations()
	if err != nil {
		log.Fatal(err)
	}
	if !status {
		log.Fatal(string(response))
	}

	organisations, err := organisation.Unmarshal(bytes.NewReader(response))
	if err != nil {
		log.Fatal(err)
	}

	organisation := organisations.FindByNameOrUID(name)
	if organisation.ID == "" {
		log.Fatalf("Organisation %s not found!", name)
	}

	return &organisation
}

// readFromStdinOrReadFromArgument returns the filename and a filereader for fileArg.
// If fileArg matches "-", the defaultFileName and stdin is returned
func readFromStdinOrReadFromArgument(fileArg, defaultFileName string) (fileName string, reader io.Reader, err error) {
	if fileArg == "-" {
		fileName = defaultFileName
		reader = os.Stdin
	} else {
		fileName = filepath.Base(fileArg)
		reader, err = os.OpenFile(fileArg, os.O_RDONLY, 0755)
		if err != nil {
			return "", nil, err
		}
	}

	return fileName, reader, err
}

func readTestCaseFromStdinOrReadFromArgument(arg, defaultFileName string) (fileName string, reader io.Reader, err error) {
	fileName, testCaseFile, err := readFromStdinOrReadFromArgument(arg, defaultFileName)
	if err != nil {
		log.Fatal(err)
	}

	basePath := ""
	if arg != "-" {
		basePath = filepath.Dir(arg)
	} else {
		var err error
		basePath, err = os.Getwd()
		if err != nil {
			log.Fatal(err)
		}
	}

	inputWithIncludes := processIncludes(testCaseFile, basePath)

	return fileName, inputWithIncludes, err
}

func processIncludes(input io.Reader, basePath string) io.Reader {
	pr, output := io.Pipe()
	re := regexp.MustCompile("//#include (.+?)$")

	go func() {
		defer output.Close()

		scanner := bufio.NewScanner(input)
		for scanner.Scan() {
			line := scanner.Text()

			if match := re.FindStringSubmatch(line); match != nil {
				includeFile := strings.TrimSpace(match[1])

				if !filepath.IsAbs(includeFile) {
					includeFile = filepath.Join(basePath, includeFile)
				}

				f, err := os.Open(includeFile)
				if err != nil {
					log.Fatal(err)
				}

				fmt.Fprint(output, "// == start include ("+includeFile+")\n")
				io.Copy(output, f)
				fmt.Fprint(output, "// == end include ("+includeFile+")\n")
			} else {
				fmt.Fprintln(output, line)
			}
		}
	}()

	return pr
}

func watchTestRun(testRunUID string, maxWatchTime float64, outputFormat string) {
	client := NewClient()
	started := time.Now()
	first := true
	testStarted := false
	testEnded := false

	for true {
		runningSince := time.Since(started).Seconds()

		testRun, response, err := client.TestRunWatch(testRunUID)
		if err != nil {
			log.Fatal(err)
		}

		if first {
			first = false

			if testRunSuccess(&testRun) {
				if outputFormat != "json" {
					fmt.Printf("Test Run %s is finished.\n", testRun.ID)
				}

				return
			}

			if outputFormat == "json" {
				fmt.Println(response)
			} else {
				formattedStartedAt := "not yet"
				formattedEstimatedEnd := "n/a"

				if testRun.StartedAt != "" {
					formattedStartedAt = humanize.Time(parseTime(testRun.StartedAt))
					testStarted = true
				}
				if testRun.EstimatedEnd != "" {
					formattedEstimatedEnd = humanize.Time(parseTime(testRun.EstimatedEnd))
				}

				fmt.Printf("[status] Test Run: %s started %s (est. end %s)\n", testRun.ID, formattedStartedAt, formattedEstimatedEnd)
			}
		}

		if outputFormat == "json" {
			fmt.Println(response)
		} else {
			switch testRun.State {
			case "running":
				if !testStarted {
					formattedStartedAt := "not yet"
					formattedEstimatedEnd := "n/a"

					if testRun.StartedAt != "" {
						formattedStartedAt = humanize.Time(parseTime(testRun.StartedAt))
					}
					if testRun.EstimatedEnd != "" {
						formattedEstimatedEnd = humanize.Time(parseTime(testRun.EstimatedEnd))
					}

					fmt.Printf("[status] Test Run: %s started %s (est. end %s)\n", testRun.ID, formattedStartedAt, formattedEstimatedEnd)

					testStarted = true
				}
				fmt.Printf("[%s] Progress: %d%%\n", testRun.State, testRun.Progress)
			default:
				if testStarted && !testEnded {
					fmt.Printf("[status] Test run ended...\n")
					testEnded = true
				}

				fmt.Printf("[%s]\n", testRun.State)
			}

		}

		if !testRunOkay(&testRun) {
			os.Exit(1)
		}

		if testRunSuccess(&testRun) {
			return
		}

		if int(maxWatchTime) > 0 && int(runningSince) > int(maxWatchTime) {
			os.Exit(2)
		}

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

func lookupOrganisationUID(client api.Client, input string) string {
	organisation := findOrganisationByName(client, input)
	if organisation.ID == "" {
		log.Fatalf("Organisation %s not found", input)
	}

	return organisation.ID
}

func lookupTestCase(client api.Client, input string) string {
	segments := strings.Split(input, "/")
	nameOrUID := input

	if len(segments) == 2 {
		organisationNameOrUID := segments[0]
		nameOrUID = segments[1]

		organisationUID := lookupOrganisationUID(client, organisationNameOrUID)

		_, result, err := client.ListTestCases(organisationUID, "all")
		if err != nil {
			log.Fatal(err)
		}

		testCases, err := testcase.Unmarshal(bytes.NewReader(result))
		if err != nil {
			log.Fatal(err)
		}

		testCase := testCases.FindByNameOrUID(nameOrUID)
		if testCase.ID == "" {
			log.Fatalf("Test case %s not found", nameOrUID)
		}

		return testCase.ID
	}

	return nameOrUID
}

func getTestRunUID(client api.Client, input string) string {
	testRunParts := api.ExtractTestRunResources(input)

	if testRunParts.UID != "" {
		return testRunParts.UID
	} else if testRunParts.Organisation == "" || testRunParts.TestCase == "" {
		log.Fatal("Invalid test run reference provided! Consult with --help to learn more.")
	}

	result := fetchTestRun(client, input)
	testRun, err := testrun.UnmarshalSingle(bytes.NewReader(result))
	if err != nil {
		log.Fatal(err)
	}

	return testRun.ID
}

func fetchTestRun(client api.Client, input string) []byte {
	testRunParts := api.ExtractTestRunResources(input)

	if testRunParts.UID != "" {
		status, response, err := client.FetchTestRun(testRunParts.UID)
		if err != nil {
			log.Fatal(err)
		}
		if !status {
			log.Fatalf("Could not load test run %s", testRunParts.UID)
		}

		return response
	} else if testRunParts.Organisation == "" || testRunParts.TestCase == "" {
		log.Fatal("Invalid test run reference provided! Consult with --help to learn more.")
	}

	status, response, err := client.LookupAndFetchResource("test_run", input)
	if err != nil {
		log.Fatal(err)
	}
	if !status {
		log.Fatalf("Test Run %s not found", input)
	}

	return response
}

func parseTime(subject string) time.Time {
	parsedTime, err := time.Parse(time.RFC3339Nano, subject)
	if err != nil {
		log.Fatal(err)
	}

	return parsedTime
}

func convertToLocalTZ(timeToConvert time.Time) time.Time {
	loc, err := time.LoadLocation("Local")
	if err != nil {
		log.Fatal(err)
	}

	return timeToConvert.In(loc)
}

func runNfrCheck(client api.Client, testRunUID string, fileName string, nfrData io.Reader) {
	status, result, err := client.TestRunNfrCheck(testRunUID, fileName, nfrData)
	if err != nil {
		log.Fatal(err)
	}

	if rootOpts.OutputFormat == "json" {
		fmt.Println(string(result))
		return
	}

	if !status {
		var response struct {
			Status  string
			Message string
			Error   string
		}

		log.Println("Could not perform test-run NFR checks...")
		if err := json.Unmarshal(result, &response); err != nil {
			log.Println(string(result))
		} else {
			if response.Status != "" {
				log.Printf(" Status:\t%s\n", response.Status)
			}
			if response.Message != "" {
				log.Printf(" Message:\t%s\n", response.Message)
			}
			if response.Error != "" {
				log.Printf(" Error:\t%s\n", response.Error)
			}
		}
		os.Exit(1)
	}

	items, err := testrun.UnmarshalNfrResults(bytes.NewReader(result))
	if err != nil {
		log.Fatal(err)
	}

	anyFails := displayNfrResult(items)

	if anyFails {
		os.Exit(1)
	}
}

func displayNfrResult(items testrun.NfrResultList) bool {
	green := color.New(color.FgGreen).SprintFunc()
	red := color.New(color.FgRed).SprintFunc()
	redBg := color.New(color.BgRed).Add(color.FgWhite).SprintFunc()
	white := color.New(color.FgWhite).SprintFunc()
	whiteBold := color.New(color.FgWhite, color.Bold).SprintFunc()

	checkStatus := ""
	anyFails := false
	var success, total int
	for _, item := range items.NfrResults {
		total++ // we count everything, including disable and unavailable here.
		if !item.Disabled {
			actualSubject := ""

			if item.SubjectAvailable {
				if item.Success {
					success++
					checkStatus = green("\u2713")
					actualSubject = fmt.Sprintf("was %s", item.SubjectWithUnit())
				} else {
					anyFails = true
					checkStatus = red("\u2717")
					actualSubject = fmt.Sprintf("but actually was %s", item.SubjectWithUnit())
				}
			} else {
				checkStatus = whiteBold("?")
				actualSubject = fmt.Sprintf("was %s", whiteBold("not available"))
			}

			filter := ""
			if item.Filter != "null" && item.Filter != "" {
				filter = " (where: " + item.Filter + ")"
			}

			fmt.Printf(
				"%s %s expected to be %s; %s (%s)%s\n",
				checkStatus,
				item.Metric,
				item.ExpectationWithUnit(),
				actualSubject,
				item.Type,
				filter,
			)
		} else {
			fmt.Printf(
				"%s %s %s expected to be %s (%s)\n",
				white("?"),
				redBg("DISABLED"),
				item.Metric,
				item.ExpectationWithUnit(),
				item.Type,
			)
		}
	}

	fmt.Printf("%d/%d checks passed\n", success, total)
	if !anyFails {
		fmt.Printf(green("\nAll checks passed!\n"))
	} else {
		fmt.Printf(red("\nYou have failing checks!\n"))
	}

	return anyFails
}
<|endoftext|>