<commit_before>// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package jwalterweatherman

import (
	"fmt"
	"io"
	"log"
	"os"
)

type Threshold int

func (t Threshold) String() string {
	return prefixes[t]
}

const (
	LevelTrace Threshold = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
	LevelCritical
	LevelFatal
)

var prefixes map[Threshold]string = map[Threshold]string{
	LevelTrace:    "TRACE",
	LevelDebug:    "DEBUG",
	LevelInfo:     "INFO",
	LevelWarn:     "WARN",
	LevelError:    "ERROR",
	LevelCritical: "CRITICAL",
	LevelFatal:    "FATAL",
}

func prefix(t Threshold) string {
	return t.String() + " "
}

// Notepad is where you leave a note !
type Notepad struct {
	TRACE    *log.Logger
	DEBUG    *log.Logger
	INFO     *log.Logger
	WARN     *log.Logger
	ERROR    *log.Logger
	CRITICAL *log.Logger
	FATAL    *log.Logger

	LOG      *log.Logger
	FEEDBACK *Feedback

	loggers         []**log.Logger
	logHandle       io.Writer
	outHandle       io.Writer
	logThreshold    Threshold
	stdoutThreshold Threshold
	prefix          string
	flags           int

	// One per Threshold
	logCounters [7]*logCounter
}

// NewNotepad create a new notepad.
func NewNotepad(outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int) *Notepad {
	n := &Notepad{}

	n.loggers = append(n.loggers, &n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL)
	n.outHandle = outHandle
	n.logHandle = logHandle
	n.stdoutThreshold = outThreshold
	n.logThreshold = logThreshold

	if len(prefix) != 0 {
		n.prefix = "[" + prefix + "] "
	} else {
		n.prefix = ""
	}

	n.flags = flags

	n.LOG = log.New(n.logHandle,
		"LOG:   ",
		n.flags)
	n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG}

	n.init()
	return n
}

// init creates the loggers for each level depending on the notepad thresholds.
func (n *Notepad) init() {
	bothHandle := io.MultiWriter(n.outHandle, n.logHandle)

	for t, logger := range n.loggers {
		threshold := Threshold(t)
		counter := &logCounter{}
		n.logCounters[t] = counter

		switch {
		case threshold >= n.logThreshold && threshold >= n.stdoutThreshold:
			*logger = log.New(io.MultiWriter(counter, bothHandle), n.prefix+prefix(threshold), n.flags)

		case threshold >= n.logThreshold:
			*logger = log.New(io.MultiWriter(counter, n.logHandle), n.prefix+prefix(threshold), n.flags)

		case threshold >= n.stdoutThreshold:
			*logger = log.New(io.MultiWriter(counter, os.Stdout), n.prefix+prefix(threshold), n.flags)

		default:
			*logger = log.New(counter, n.prefix+prefix(threshold), n.flags)
		}
	}
}

// SetLogThreshold change the threshold above which messages are written to the
// log file
func (n *Notepad) SetLogThreshold(threshold Threshold) {
	n.logThreshold = threshold
	n.init()
}

// SetLogOutput change the file where log messages are written
func (n *Notepad) SetLogOutput(handle io.Writer) {
	n.logHandle = handle
	n.init()
}

// GetStdoutThreshold returns the defined Treshold for the log logger.
func (n *Notepad) GetLogThreshold() Threshold {
	return n.logThreshold
}

// SetStdoutThreshold change the threshold above which messages are written to the
// standard output
func (n *Notepad) SetStdoutThreshold(threshold Threshold) {
	n.stdoutThreshold = threshold
	n.init()
}

// GetStdoutThreshold returns the Treshold for the stdout logger.
func (n *Notepad) GetStdoutThreshold() Threshold {
	return n.stdoutThreshold
}

// SetPrefix change the prefix used by the notepad. Prefixes are displayed between
// brackets at the begining of the line. An empty prefix won't be displayed at all.
func (n *Notepad) SetPrefix(prefix string) {
	if len(prefix) != 0 {
		n.prefix = "[" + prefix + "] "
	} else {
		n.prefix = ""
	}
	n.init()
}

// SetFlags choose which flags the logger will display (after prefix and message
// level). See the package log for more informations on this.
func (n *Notepad) SetFlags(flags int) {
	n.flags = flags
	n.init()
}

// Feedback writes plainly to the outHandle while
// logging with the standard extra information (date, file, etc).
type Feedback struct {
	out *log.Logger
	log *log.Logger
}

func (fb *Feedback) Println(v ...interface{}) {
	fb.output(fmt.Sprintln(v...))
}

func (fb *Feedback) Printf(format string, v ...interface{}) {
	fb.output(fmt.Sprintf(format, v...))
}

func (fb *Feedback) Print(v ...interface{}) {
	fb.output(fmt.Sprint(v...))
}

func (fb *Feedback) output(s string) {
	if fb.out != nil {
		fb.out.Output(2, s)
	}
	if fb.log != nil {
		fb.log.Output(2, s)
	}
}
<commit_msg>Get rid of prefix function<commit_after>// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package jwalterweatherman

import (
	"fmt"
	"io"
	"log"
	"os"
)

type Threshold int

func (t Threshold) String() string {
	return prefixes[t]
}

const (
	LevelTrace Threshold = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
	LevelCritical
	LevelFatal
)

var prefixes map[Threshold]string = map[Threshold]string{
	LevelTrace:    "TRACE",
	LevelDebug:    "DEBUG",
	LevelInfo:     "INFO",
	LevelWarn:     "WARN",
	LevelError:    "ERROR",
	LevelCritical: "CRITICAL",
	LevelFatal:    "FATAL",
}

// Notepad is where you leave a note!
type Notepad struct {
	TRACE    *log.Logger
	DEBUG    *log.Logger
	INFO     *log.Logger
	WARN     *log.Logger
	ERROR    *log.Logger
	CRITICAL *log.Logger
	FATAL    *log.Logger

	LOG      *log.Logger
	FEEDBACK *Feedback

	loggers         []**log.Logger
	logHandle       io.Writer
	outHandle       io.Writer
	logThreshold    Threshold
	stdoutThreshold Threshold
	prefix          string
	flags           int

	// One per Threshold
	logCounters [7]*logCounter
}

// NewNotepad create a new notepad.
func NewNotepad(outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int) *Notepad {
	n := &Notepad{}

	n.loggers = append(n.loggers, &n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL)
	n.outHandle = outHandle
	n.logHandle = logHandle
	n.stdoutThreshold = outThreshold
	n.logThreshold = logThreshold

	if len(prefix) != 0 {
		n.prefix = "[" + prefix + "] "
	} else {
		n.prefix = ""
	}

	n.flags = flags

	n.LOG = log.New(n.logHandle,
		"LOG:   ",
		n.flags)
	n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG}

	n.init()
	return n
}

// init creates the loggers for each level depending on the notepad thresholds.
func (n *Notepad) init() {
	bothHandle := io.MultiWriter(n.outHandle, n.logHandle)

	for t, logger := range n.loggers {
		threshold := Threshold(t)
		counter := &logCounter{}
		n.logCounters[t] = counter
		prefix := n.prefix + threshold.String() + " "

		switch {
		case threshold >= n.logThreshold && threshold >= n.stdoutThreshold:
			*logger = log.New(io.MultiWriter(counter, bothHandle), prefix, n.flags)

		case threshold >= n.logThreshold:
			*logger = log.New(io.MultiWriter(counter, n.logHandle), prefix, n.flags)

		case threshold >= n.stdoutThreshold:
			*logger = log.New(io.MultiWriter(counter, os.Stdout), prefix, n.flags)

		default:
			*logger = log.New(counter, prefix, n.flags)
		}
	}
}

// SetLogThreshold changes the threshold above which messages are written to the
// log file
func (n *Notepad) SetLogThreshold(threshold Threshold) {
	n.logThreshold = threshold
	n.init()
}

// SetLogOutput changes the file where log messages are written
func (n *Notepad) SetLogOutput(handle io.Writer) {
	n.logHandle = handle
	n.init()
}

// GetStdoutThreshold returns the defined Treshold for the log logger.
func (n *Notepad) GetLogThreshold() Threshold {
	return n.logThreshold
}

// SetStdoutThreshold change the threshold above which messages are written to the
// standard output
func (n *Notepad) SetStdoutThreshold(threshold Threshold) {
	n.stdoutThreshold = threshold
	n.init()
}

// GetStdoutThreshold returns the Treshold for the stdout logger.
func (n *Notepad) GetStdoutThreshold() Threshold {
	return n.stdoutThreshold
}

// SetPrefix change the prefix used by the notepad. Prefixes are displayed between
// brackets at the begining of the line. An empty prefix won't be displayed at all.
func (n *Notepad) SetPrefix(prefix string) {
	if len(prefix) != 0 {
		n.prefix = "[" + prefix + "] "
	} else {
		n.prefix = ""
	}
	n.init()
}

// SetFlags choose which flags the logger will display (after prefix and message
// level). See the package log for more informations on this.
func (n *Notepad) SetFlags(flags int) {
	n.flags = flags
	n.init()
}

// Feedback writes plainly to the outHandle while
// logging with the standard extra information (date, file, etc).
type Feedback struct {
	out *log.Logger
	log *log.Logger
}

func (fb *Feedback) Println(v ...interface{}) {
	fb.output(fmt.Sprintln(v...))
}

func (fb *Feedback) Printf(format string, v ...interface{}) {
	fb.output(fmt.Sprintf(format, v...))
}

func (fb *Feedback) Print(v ...interface{}) {
	fb.output(fmt.Sprint(v...))
}

func (fb *Feedback) output(s string) {
	if fb.out != nil {
		fb.out.Output(2, s)
	}
	if fb.log != nil {
		fb.log.Output(2, s)
	}
}
<|endoftext|>


<commit_before>package models

import (
	"time"
)

//go:generate stringer -type=JobStatus
type JobStatus int

const (
	WaitingToRun JobStatus = iota + 1
	Running
	Finished
	CannotRun
)

type Job struct {
	Id         int64     `sql:"AUTO_INCREMENT"`
	Command    string    `sql:"size:1000;not null"`
	Args       string    `sql:"size:1000"`
	Status     JobStatus `sql:"size:1;not null"`
	ExitStatus int       `sql:"size:4`
	CreatedAt  time.Time `sql:"DEFAULT:current_timestamp;not null"`
	FinishedAt time.Time
}

//go:generate stringer -type=JobMessageType
type JobMessageType int

const (
	Normal JobMessageType = iota + 1
	Error
)

type JobMessage struct {
	JobId     int64          `gorm:"primary_key" sql:"type:bigint"`
	Seq       int64          `gorm:"primary_key" sql:"type:bigint"`
	Type      JobMessageType `sql:"size:1;not null"`
	Message   string         `sql:"size:4000"`
	CreatedAt time.Time      `sql:"DEFAULT:current_timestamp;not null"`
}

type ApiKey struct {
	Id         int64     `sql:"AUTO_INCREMENT"`
	ClientName string    `sql:"size:100;not null"`
	ApiKey     string    `sql:"size:256;not null"`
	CreatedAt  time.Time `sql:"DEFAULT:current_timestamp;not null"`
}
<commit_msg>Change ApiKey.ApiKey size and add unique index<commit_after>package models

import (
	"time"
)

//go:generate stringer -type=JobStatus
type JobStatus int

const (
	WaitingToRun JobStatus = iota + 1
	Running
	Finished
	CannotRun
)

type Job struct {
	Id         int64     `sql:"AUTO_INCREMENT"`
	Command    string    `sql:"size:1000;not null"`
	Args       string    `sql:"size:1000"`
	Status     JobStatus `sql:"size:1;not null"`
	ExitStatus int       `sql:"size:4`
	CreatedAt  time.Time `sql:"DEFAULT:current_timestamp;not null"`
	FinishedAt time.Time
}

//go:generate stringer -type=JobMessageType
type JobMessageType int

const (
	Normal JobMessageType = iota + 1
	Error
)

type JobMessage struct {
	JobId     int64          `gorm:"primary_key" sql:"type:bigint"`
	Seq       int64          `gorm:"primary_key" sql:"type:bigint"`
	Type      JobMessageType `sql:"size:1;not null"`
	Message   string         `sql:"size:4000"`
	CreatedAt time.Time      `sql:"DEFAULT:current_timestamp;not null"`
}

type ApiKey struct {
	Id         int64     `sql:"AUTO_INCREMENT"`
	ClientName string    `sql:"size:100;not null"`
	ApiKey     string    `sql:"size:128;not null;unique_index"`
	CreatedAt  time.Time `sql:"DEFAULT:current_timestamp;not null"`
}
<|endoftext|>


<commit_before>package models

import (
	"net/http"
	"time"

	"github.com/NyaaPantsu/nyaa/config"
)

// TorrentReport model
// User can be null (anonymous reports)
// FIXME  can't preload field Torrents for models.TorrentReport
type TorrentReport struct {
	ID          uint   `gorm:"column:torrent_report_id;primary_key"`
	Description string `gorm:"column:type"`
	TorrentID   uint   `gorm:"column:torrent_id"`
	UserID      uint   `gorm:"column:user_id"`

	CreatedAt time.Time `gorm:"column:created_at"`

	Torrent *Torrent `gorm:"AssociationForeignKey:TorrentID;ForeignKey:torrent_id"`
	User    *User    `gorm:"AssociationForeignKey:UserID;ForeignKey:user_id"`
}

// TableName : Return the name of torrent report table
func (report TorrentReport) TableName() string {
	return config.Get().Models.ReportsTableName
}

// TorrentReportJSON : Json struct of torrent report model
type TorrentReportJSON struct {
	ID          uint        `json:"id"`
	Description string      `json:"description"`
	Torrent     TorrentJSON `json:"torrent"`
	User        UserJSON    `json:"user"`
}

// ToJSON : conversion to json of a torrent report
func (report *TorrentReport) ToJSON() TorrentReportJSON {
	t := TorrentJSON{}
	if report.Torrent != nil { // FIXME: report.Torrent should never be nil
		t = report.Torrent.ToJSON()
	}
	u := UserJSON{}
	if report.User != nil {
		u = report.User.ToJSON()
	}
	json := TorrentReportJSON{report.ID, report.Description, t, u}
	return json
}

// TorrentReportsToJSON : Conversion of multiple reports to json
func TorrentReportsToJSON(reports []TorrentReport) []TorrentReportJSON {
	json := make([]TorrentReportJSON, len(reports))
	for i := range reports {
		json[i] = reports[i].ToJSON()
	}
	return json
}

// Delete : Delete torrent report
func (report *TorrentReport) Delete(definitely bool) (int, error) {
	if definitely {
		return 0, ORM.Unscoped().Delete(report).Error
	}
	return http.StatusOK, nil
}
<commit_msg>Update report.go<commit_after>package models

import (
	"net/http"
	"time"

	"github.com/NyaaPantsu/nyaa/config"
)

// TorrentReport model
// User can be null (anonymous reports)
// FIXME  can't preload field Torrents for models.TorrentReport
type TorrentReport struct {
	ID 	        uint   `gorm:"column:torrent_report_id;primary_key"`
	Description string `gorm:"column:type"`
	Message     string `gorm:"column:message"`
	TorrentID   uint   `gorm:"column:torrent_id"`
	UserID      uint   `gorm:"column:user_id"`

	CreatedAt time.Time `gorm:"column:created_at"`

	Torrent *Torrent `gorm:"AssociationForeignKey:TorrentID;ForeignKey:torrent_id"`
	User    *User    `gorm:"AssociationForeignKey:UserID;ForeignKey:user_id"`
}

// TableName : Return the name of torrent report table
func (report TorrentReport) TableName() string {
	return config.Get().Models.ReportsTableName
}

// TorrentReportJSON : Json struct of torrent report model
type TorrentReportJSON struct {
	ID          uint        `json:"id"`
	Description string      `json:"description"`
	Message     string      `json:"message"`
	Torrent     TorrentJSON `json:"torrent"`
	User        UserJSON    `json:"user"`
}

// ToJSON : conversion to json of a torrent report
func (report *TorrentReport) ToJSON() TorrentReportJSON {
	t := TorrentJSON{}
	if report.Torrent != nil { // FIXME: report.Torrent should never be nil
		t = report.Torrent.ToJSON()
	}
	u := UserJSON{}
	if report.User != nil {
		u = report.User.ToJSON()
	}
	json := TorrentReportJSON{report.ID, report.Description, report.Message, t, u}
	return json
}

// TorrentReportsToJSON : Conversion of multiple reports to json
func TorrentReportsToJSON(reports []TorrentReport) []TorrentReportJSON {
	json := make([]TorrentReportJSON, len(reports))
	for i := range reports {
		json[i] = reports[i].ToJSON()
	}
	return json
}

// Delete : Delete torrent report
func (report *TorrentReport) Delete(definitely bool) (int, error) {
	if definitely {
		return 0, ORM.Unscoped().Delete(report).Error
	}
	return http.StatusOK, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package checkmgr provides a check management interace to circonus-gometrics
package checkmgr

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/circonus-labs/circonus-gometrics/api"
)

// Check management offers:
//
// Create a check if one cannot be found matching specific criteria
// Manage metrics in the supplied check (enabling new metrics as they are submitted)
//
// To disable check management, leave Config.Api.Token.Key blank
//
// use cases:
// configure without api token - check management disabled
//  - configuration parameters other than Check.SubmissionUrl, Debug and Log are ignored
//  - note: SubmissionUrl is **required** in this case as there is no way to derive w/o api
// configure with api token - check management enabled
//  - all otehr configuration parameters affect how the trap url is obtained
//    1. provided (Check.SubmissionUrl)
//    2. via check lookup (CheckConfig.Id)
//    3. via a search using CheckConfig.InstanceId + CheckConfig.SearchTag
//    4. a new check is created

const (
	defaultCheckType             = "httptrap"
	defaultTrapMaxURLAge         = "60s"   // 60 seconds
	defaultBrokerMaxResponseTime = "500ms" // 500 milliseconds
	defaultForceMetricActivation = "false"
	statusActive                 = "active"
)

// CheckConfig options for check
type CheckConfig struct {
	// a specific submission url
	SubmissionURL string
	// a specific check id (not check bundle id)
	ID string
	// unique instance id string
	// used to search for a check to use
	// used as check.target when creating a check
	InstanceID string
	// unique check searching tag (or tags)
	// used to search for a check to use (combined with instanceid)
	// used as a regular tag when creating a check
	SearchTag string
	// a custom display name for the check (as viewed in UI Checks)
	DisplayName string
	// httptrap check secret (for creating a check)
	Secret string
	// additional tags to add to a check (when creating a check)
	// these tags will not be added to an existing check
	Tags string
	// max amount of time to to hold on to a submission url
	// when a given submission fails (due to retries) if the
	// time the url was last updated is > than this, the trap
	// url will be refreshed (e.g. if the broker is changed
	// in the UI) **only relevant when check management is enabled**
	// e.g. 5m, 30m, 1h, etc.
	MaxURLAge string
	// force metric activation - if a metric has been disabled via the UI
	// the default behavior is to *not* re-activate the metric; this setting
	// overrides the behavior and will re-activate the metric when it is
	// encountered. "(true|false)", default "false"
	ForceMetricActivation string
}

// BrokerConfig options for broker
type BrokerConfig struct {
	// a specific broker id (numeric portion of cid)
	ID string
	// one or more tags used to select 1-n brokers from which to select
	// when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc)
	SelectTag string
	// for a broker to be considered viable it must respond to a
	// connection attempt within this amount of time e.g. 200ms, 2s, 1m
	MaxResponseTime string
}

// Config options
type Config struct {
	Log   *log.Logger
	Debug bool

	// Circonus API config
	API api.Config
	// Check specific configuration options
	Check CheckConfig
	// Broker specific configuration options
	Broker BrokerConfig
}

// CheckTypeType check type
type CheckTypeType string

// CheckInstanceIDType check instance id
type CheckInstanceIDType string

// CheckSecretType check secret
type CheckSecretType string

// CheckTagsType check tags
type CheckTagsType string

// CheckDisplayNameType check display name
type CheckDisplayNameType string

// BrokerCNType broker common name
type BrokerCNType string

// CheckManager settings
type CheckManager struct {
	enabled bool
	Log     *log.Logger
	Debug   bool
	apih    *api.API

	// check
	checkType             CheckTypeType
	checkID               api.IDType
	checkInstanceID       CheckInstanceIDType
	checkSearchTag        api.TagType
	checkSecret           CheckSecretType
	checkTags             api.TagType
	checkSubmissionURL    api.URLType
	checkDisplayName      CheckDisplayNameType
	forceMetricActivation bool

	// broker
	brokerID              api.IDType
	brokerSelectTag       api.TagType
	brokerMaxResponseTime time.Duration

	// state
	checkBundle      *api.CheckBundle
	availableMetrics map[string]bool
	trapURL          api.URLType
	trapCN           BrokerCNType
	trapLastUpdate   time.Time
	trapMaxURLAge    time.Duration
	trapmu           sync.Mutex
	certPool         *x509.CertPool
}

// Trap config
type Trap struct {
	URL *url.URL
	TLS *tls.Config
}

// NewCheckManager returns a new check manager
func NewCheckManager(cfg *Config) (*CheckManager, error) {

	if cfg == nil {
		return nil, errors.New("Invalid Check Manager configuration (nil).")
	}

	cm := &CheckManager{
		enabled: false,
	}

	cm.Debug = cfg.Debug

	cm.Log = cfg.Log
	if cm.Log == nil {
		if cm.Debug {
			cm.Log = log.New(os.Stderr, "", log.LstdFlags)
		} else {
			cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
		}
	}

	if cfg.Check.SubmissionURL != "" {
		cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL)
	}
	// Blank API Token *disables* check management
	if cfg.API.TokenKey == "" {
		if cm.checkSubmissionURL == "" {
			return nil, errors.New("Invalid check manager configuration (no API token AND no submission url).")
		}
		if err := cm.initializeTrapURL(); err != nil {
			return nil, err
		}
		return cm, nil
	}

	// enable check manager

	cm.enabled = true

	// initialize api handle

	cfg.API.Debug = cm.Debug
	cfg.API.Log = cm.Log

	apih, err := api.NewAPI(&cfg.API)
	if err != nil {
		return nil, err
	}
	cm.apih = apih

	// initialize check related data

	cm.checkType = defaultCheckType

	idSetting := "0"
	if cfg.Check.ID != "" {
		idSetting = cfg.Check.ID
	}
	id, err := strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.checkID = api.IDType(id)

	cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID)
	cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName)
	cm.checkSecret = CheckSecretType(cfg.Check.Secret)

	fma := defaultForceMetricActivation
	if cfg.Check.ForceMetricActivation != "" {
		fma = cfg.Check.ForceMetricActivation
	}
	fm, err := strconv.ParseBool(fma)
	if err != nil {
		return nil, err
	}
	cm.forceMetricActivation = fm

	_, an := path.Split(os.Args[0])
	hn, err := os.Hostname()
	if err != nil {
		hn = "unknown"
	}
	if cm.checkInstanceID == "" {
		cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an))
	}

	if cfg.Check.SearchTag == "" {
		cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)}
	} else {
		cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",")
	}

	if cfg.Check.Tags != "" {
		cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",")
	}

	if cm.checkDisplayName == "" {
		cm.checkDisplayName = CheckDisplayNameType(fmt.Sprintf("%s /cgm", string(cm.checkInstanceID)))
	}

	dur := cfg.Check.MaxURLAge
	if dur == "" {
		dur = defaultTrapMaxURLAge
	}
	maxDur, err := time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.trapMaxURLAge = maxDur

	// setup broker

	idSetting = "0"
	if cfg.Broker.ID != "" {
		idSetting = cfg.Broker.ID
	}
	id, err = strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.brokerID = api.IDType(id)

	if cfg.Broker.SelectTag != "" {
		cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",")
	}

	dur = cfg.Broker.MaxResponseTime
	if dur == "" {
		dur = defaultBrokerMaxResponseTime
	}
	maxDur, err = time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.brokerMaxResponseTime = maxDur

	// metrics
	cm.availableMetrics = make(map[string]bool)

	if err := cm.initializeTrapURL(); err != nil {
		return nil, err
	}

	return cm, nil
}

// GetTrap return the trap url
func (cm *CheckManager) GetTrap() (*Trap, error) {
	if cm.trapURL == "" {
		if err := cm.initializeTrapURL(); err != nil {
			return nil, err
		}
	}

	trap := &Trap{}

	u, err := url.Parse(string(cm.trapURL))
	if err != nil {
		return nil, err
	}

	trap.URL = u

	if u.Scheme == "https" {
		if cm.certPool == nil {
			cm.loadCACert()
		}
		t := &tls.Config{
			RootCAs: cm.certPool,
		}
		if cm.trapCN != "" {
			t.ServerName = string(cm.trapCN)
		}
		trap.TLS = t
	}

	return trap, nil
}

// ResetTrap URL, force request to the API for the submission URL and broker ca cert
func (cm *CheckManager) ResetTrap() error {
	if cm.trapURL == "" {
		return nil
	}

	cm.trapURL = ""
	cm.certPool = nil
	err := cm.initializeTrapURL()
	return err
}

// RefreshTrap check when the last time the URL was reset, reset if needed
func (cm *CheckManager) RefreshTrap() {
	if cm.trapURL == "" {
		return
	}

	if time.Since(cm.trapLastUpdate) >= cm.trapMaxURLAge {
		cm.ResetTrap()
	}
}
<commit_msg>new: add support for metric tagging<commit_after>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package checkmgr provides a check management interace to circonus-gometrics
package checkmgr

import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/circonus-labs/circonus-gometrics/api"
)

// Check management offers:
//
// Create a check if one cannot be found matching specific criteria
// Manage metrics in the supplied check (enabling new metrics as they are submitted)
//
// To disable check management, leave Config.Api.Token.Key blank
//
// use cases:
// configure without api token - check management disabled
//  - configuration parameters other than Check.SubmissionUrl, Debug and Log are ignored
//  - note: SubmissionUrl is **required** in this case as there is no way to derive w/o api
// configure with api token - check management enabled
//  - all otehr configuration parameters affect how the trap url is obtained
//    1. provided (Check.SubmissionUrl)
//    2. via check lookup (CheckConfig.Id)
//    3. via a search using CheckConfig.InstanceId + CheckConfig.SearchTag
//    4. a new check is created

const (
	defaultCheckType             = "httptrap"
	defaultTrapMaxURLAge         = "60s"   // 60 seconds
	defaultBrokerMaxResponseTime = "500ms" // 500 milliseconds
	defaultForceMetricActivation = "false"
	statusActive                 = "active"
)

// CheckConfig options for check
type CheckConfig struct {
	// a specific submission url
	SubmissionURL string
	// a specific check id (not check bundle id)
	ID string
	// unique instance id string
	// used to search for a check to use
	// used as check.target when creating a check
	InstanceID string
	// unique check searching tag (or tags)
	// used to search for a check to use (combined with instanceid)
	// used as a regular tag when creating a check
	SearchTag string
	// a custom display name for the check (as viewed in UI Checks)
	DisplayName string
	// httptrap check secret (for creating a check)
	Secret string
	// additional tags to add to a check (when creating a check)
	// these tags will not be added to an existing check
	Tags string
	// max amount of time to to hold on to a submission url
	// when a given submission fails (due to retries) if the
	// time the url was last updated is > than this, the trap
	// url will be refreshed (e.g. if the broker is changed
	// in the UI) **only relevant when check management is enabled**
	// e.g. 5m, 30m, 1h, etc.
	MaxURLAge string
	// force metric activation - if a metric has been disabled via the UI
	// the default behavior is to *not* re-activate the metric; this setting
	// overrides the behavior and will re-activate the metric when it is
	// encountered. "(true|false)", default "false"
	ForceMetricActivation string
}

// BrokerConfig options for broker
type BrokerConfig struct {
	// a specific broker id (numeric portion of cid)
	ID string
	// one or more tags used to select 1-n brokers from which to select
	// when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc)
	SelectTag string
	// for a broker to be considered viable it must respond to a
	// connection attempt within this amount of time e.g. 200ms, 2s, 1m
	MaxResponseTime string
}

// Config options
type Config struct {
	Log   *log.Logger
	Debug bool

	// Circonus API config
	API api.Config
	// Check specific configuration options
	Check CheckConfig
	// Broker specific configuration options
	Broker BrokerConfig
}

// CheckTypeType check type
type CheckTypeType string

// CheckInstanceIDType check instance id
type CheckInstanceIDType string

// CheckSecretType check secret
type CheckSecretType string

// CheckTagsType check tags
type CheckTagsType string

// CheckDisplayNameType check display name
type CheckDisplayNameType string

// BrokerCNType broker common name
type BrokerCNType string

// CheckManager settings
type CheckManager struct {
	enabled bool
	Log     *log.Logger
	Debug   bool
	apih    *api.API

	// check
	checkType             CheckTypeType
	checkID               api.IDType
	checkInstanceID       CheckInstanceIDType
	checkSearchTag        api.TagType
	checkSecret           CheckSecretType
	checkTags             api.TagType
	checkSubmissionURL    api.URLType
	checkDisplayName      CheckDisplayNameType
	forceMetricActivation bool
	forceCheckUpdate      bool

	// metric tags
	metricTags map[string][]string
	mtmu       sync.Mutex

	// broker
	brokerID              api.IDType
	brokerSelectTag       api.TagType
	brokerMaxResponseTime time.Duration

	// state
	checkBundle      *api.CheckBundle
	cbmu             sync.Mutex
	availableMetrics map[string]bool
	trapURL          api.URLType
	trapCN           BrokerCNType
	trapLastUpdate   time.Time
	trapMaxURLAge    time.Duration
	trapmu           sync.Mutex
	certPool         *x509.CertPool
}

// Trap config
type Trap struct {
	URL *url.URL
	TLS *tls.Config
}

// NewCheckManager returns a new check manager
func NewCheckManager(cfg *Config) (*CheckManager, error) {

	if cfg == nil {
		return nil, errors.New("Invalid Check Manager configuration (nil).")
	}

	cm := &CheckManager{
		enabled: false,
	}

	cm.Debug = cfg.Debug
	cm.Log = cfg.Log
	if cm.Debug && cm.Log == nil {
		cm.Log = log.New(os.Stderr, "", log.LstdFlags)
	}
	if cm.Log == nil {
		cm.Log = log.New(ioutil.Discard, "", log.LstdFlags)
	}

	if cfg.Check.SubmissionURL != "" {
		cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL)
	}
	// Blank API Token *disables* check management
	if cfg.API.TokenKey == "" {
		if cm.checkSubmissionURL == "" {
			return nil, errors.New("Invalid check manager configuration (no API token AND no submission url).")
		}
		if err := cm.initializeTrapURL(); err != nil {
			return nil, err
		}
		return cm, nil
	}

	// enable check manager

	cm.enabled = true

	// initialize api handle

	cfg.API.Debug = cm.Debug
	cfg.API.Log = cm.Log

	apih, err := api.NewAPI(&cfg.API)
	if err != nil {
		return nil, err
	}
	cm.apih = apih

	// initialize check related data

	cm.checkType = defaultCheckType

	idSetting := "0"
	if cfg.Check.ID != "" {
		idSetting = cfg.Check.ID
	}
	id, err := strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.checkID = api.IDType(id)

	cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID)
	cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName)
	cm.checkSecret = CheckSecretType(cfg.Check.Secret)

	fma := defaultForceMetricActivation
	if cfg.Check.ForceMetricActivation != "" {
		fma = cfg.Check.ForceMetricActivation
	}
	fm, err := strconv.ParseBool(fma)
	if err != nil {
		return nil, err
	}
	cm.forceMetricActivation = fm

	_, an := path.Split(os.Args[0])
	hn, err := os.Hostname()
	if err != nil {
		hn = "unknown"
	}
	if cm.checkInstanceID == "" {
		cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an))
	}

	if cfg.Check.SearchTag == "" {
		cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)}
	} else {
		cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",")
	}

	if cfg.Check.Tags != "" {
		cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",")
	}

	if cm.checkDisplayName == "" {
		cm.checkDisplayName = CheckDisplayNameType(fmt.Sprintf("%s /cgm", string(cm.checkInstanceID)))
	}

	dur := cfg.Check.MaxURLAge
	if dur == "" {
		dur = defaultTrapMaxURLAge
	}
	maxDur, err := time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.trapMaxURLAge = maxDur

	// setup broker

	idSetting = "0"
	if cfg.Broker.ID != "" {
		idSetting = cfg.Broker.ID
	}
	id, err = strconv.Atoi(idSetting)
	if err != nil {
		return nil, err
	}
	cm.brokerID = api.IDType(id)

	if cfg.Broker.SelectTag != "" {
		cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",")
	}

	dur = cfg.Broker.MaxResponseTime
	if dur == "" {
		dur = defaultBrokerMaxResponseTime
	}
	maxDur, err = time.ParseDuration(dur)
	if err != nil {
		return nil, err
	}
	cm.brokerMaxResponseTime = maxDur

	// metrics
	cm.availableMetrics = make(map[string]bool)
	cm.metricTags = make(map[string][]string)

	if err := cm.initializeTrapURL(); err != nil {
		return nil, err
	}

	return cm, nil
}

// GetTrap return the trap url
func (cm *CheckManager) GetTrap() (*Trap, error) {
	if cm.trapURL == "" {
		if err := cm.initializeTrapURL(); err != nil {
			return nil, err
		}
	}

	trap := &Trap{}

	u, err := url.Parse(string(cm.trapURL))
	if err != nil {
		return nil, err
	}

	trap.URL = u

	if u.Scheme == "https" {
		if cm.certPool == nil {
			cm.loadCACert()
		}
		t := &tls.Config{
			RootCAs: cm.certPool,
		}
		if cm.trapCN != "" {
			t.ServerName = string(cm.trapCN)
		}
		trap.TLS = t
	}

	return trap, nil
}

// ResetTrap URL, force request to the API for the submission URL and broker ca cert
func (cm *CheckManager) ResetTrap() error {
	if cm.trapURL == "" {
		return nil
	}

	cm.trapURL = ""
	cm.certPool = nil
	err := cm.initializeTrapURL()
	return err
}

// RefreshTrap check when the last time the URL was reset, reset if needed
func (cm *CheckManager) RefreshTrap() {
	if cm.trapURL == "" {
		return
	}

	if time.Since(cm.trapLastUpdate) >= cm.trapMaxURLAge {
		cm.ResetTrap()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo"

	"github.com/mryp/squidgirl-go/db"
)

//LoginRequest はログインリクエストデータ構造体
type LoginRequest struct {
	UserName string `json:"username" xml:"username" form:"username" query:"username"`
	Password string `json:"password" xml:"password" form:"password" query:"password"`
}

//LoginResponce はログインレスポンスデータ構造体
type LoginResponce struct {
	Token string `json:"token" xml:"token"`
}

//CreateUserRequest はユーザー作成リクエストデータ構造体
type CreateUserRequest struct {
	UserName  string `json:"username" xml:"username" form:"username" query:"username"`
	Password  string `json:"password" xml:"password" form:"password" query:"password"`
	AuthLevel int    `json:"authlevel" xml:"authlevel" form:"authlevel" query:"authlevel"`
}

//CreateUserResponce はユーザー作成レスポンス構造体
type CreateUserResponce struct {
	Status int `json:"status" xml:"status"`
}

//DeleteUserRequest はユーザー削除リクエスト構造体
type DeleteUserRequest struct {
	UserName string `json:"username" xml:"username" form:"username" query:"username"`
}

//DeleteUserResponce はユーザー削除レスポンス構造体
type DeleteUserResponce struct {
	Status int `json:"status" xml:"status"`
}

//UserListResponce はユーザー一覧取得リクエスト構造体
type UserListResponce struct {
	Count int                     `json:"count" xml:"count"`
	Users []UserListUsersResponce `json:"users" xml:"users"`
}

//UserListUsersResponce はユーザー一覧取得レスポンス構造体
type UserListUsersResponce struct {
	UserName  string `json:"username" xml:"username"`
	AuthLevel int    `json:"authlevel" xml:"authlevel"`
}

//LoginHandler はユーザーログインを行い、トークンを返す
func LoginHandler(c echo.Context) error {
	req := new(LoginRequest)
	if err := c.Bind(req); err != nil {
		return err
	}
	fmt.Printf("request=%v\n", *req)

	loginUser, err := NewLoginUserFromDB(req.UserName, req.Password)
	if err != nil {
		return echo.NewHTTPError(http.StatusUnauthorized)
	}

	token, err := loginUser.CreateLoginToken()
	if err != nil {
		return echo.NewHTTPError(http.StatusUnauthorized)
	}

	fmt.Printf("token=%s\n", token)
	res := new(LoginResponce)
	res.Token = token
	return c.JSON(http.StatusOK, res)
}

//CreateUserHandler は指定したユーザーを作成する
func CreateUserHandler(c echo.Context) error {
	req := new(CreateUserRequest)
	if err := c.Bind(req); err != nil {
		return err
	}
	fmt.Printf("request=%v\n", *req)

	user, err := db.SelectUser(req.UserName)
	if err == nil && user.ID != 0 {
		return fmt.Errorf("すでにユーザーが存在する")
	}

	err = db.InsertUser(req.UserName, req.Password, req.AuthLevel)
	if err != nil {
		return err
	}

	res := new(CreateUserResponce)
	res.Status = 0
	return c.JSON(http.StatusOK, res)
}

//DeleteUserHandler は指定したユーザーを削除する。削除するには管理者権限が必要
func DeleteUserHandler(c echo.Context) error {
	req := new(DeleteUserRequest)
	if err := c.Bind(req); err != nil {
		return err
	}
	fmt.Printf("request=%v\n", *req)

	user, err := db.SelectUser(req.UserName)
	if err != nil || user.ID == 0 {
		return fmt.Errorf("削除するユーザーが見つからない")
	}

	//ユーザーを削除
	err = db.DeleteUser(user.ID)
	if err != nil {
		return err
	}

	//履歴も削除
	err = db.DeleteHistory(user.Name)
	if err != nil {
		return err
	}

	res := new(DeleteUserResponce)
	res.Status = 0
	return c.JSON(http.StatusOK, res)
}

//UserListHandler はユーザー一覧を取得する
func UserListHandler(c echo.Context) error {
	userList, err := db.SelectUserAll()
	if err != nil {
		return err
	}

	res := new(UserListResponce)
	res.Count = len(userList)
	userResponceList := make([]UserListUsersResponce, 0)
	for _, user := range userList {
		userResponceList = append(userResponceList, UserListUsersResponce{
			UserName:  user.Name,
			AuthLevel: user.Permission,
		})
	}
	res.Users = userResponceList
	return c.JSON(http.StatusOK, res)
}
<commit_msg>ユーザー操作にログインユーザーを参照するように変更<commit_after>package main

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo"

	"github.com/mryp/squidgirl-go/db"
)

//LoginRequest はログインリクエストデータ構造体
type LoginRequest struct {
	UserName string `json:"username" xml:"username" form:"username" query:"username"`
	Password string `json:"password" xml:"password" form:"password" query:"password"`
}

//LoginResponce はログインレスポンスデータ構造体
type LoginResponce struct {
	Token string `json:"token" xml:"token"`
}

//CreateUserRequest はユーザー作成リクエストデータ構造体
type CreateUserRequest struct {
	UserName  string `json:"username" xml:"username" form:"username" query:"username"`
	Password  string `json:"password" xml:"password" form:"password" query:"password"`
	AuthLevel int    `json:"authlevel" xml:"authlevel" form:"authlevel" query:"authlevel"`
}

//CreateUserResponce はユーザー作成レスポンス構造体
type CreateUserResponce struct {
	Status int `json:"status" xml:"status"`
}

//DeleteUserRequest はユーザー削除リクエスト構造体
type DeleteUserRequest struct {
	UserName string `json:"username" xml:"username" form:"username" query:"username"`
}

//DeleteUserResponce はユーザー削除レスポンス構造体
type DeleteUserResponce struct {
	Status int `json:"status" xml:"status"`
}

//UserListResponce はユーザー一覧取得リクエスト構造体
type UserListResponce struct {
	Count int                     `json:"count" xml:"count"`
	Users []UserListUsersResponce `json:"users" xml:"users"`
}

//UserListUsersResponce はユーザー一覧取得レスポンス構造体
type UserListUsersResponce struct {
	UserName  string `json:"username" xml:"username"`
	AuthLevel int    `json:"authlevel" xml:"authlevel"`
}

//LoginHandler はユーザーログインを行い、トークンを返す
func LoginHandler(c echo.Context) error {
	req := new(LoginRequest)
	if err := c.Bind(req); err != nil {
		return err
	}
	fmt.Printf("request=%v\n", *req)

	loginUser, err := NewLoginUserFromDB(req.UserName, req.Password)
	if err != nil {
		return echo.NewHTTPError(http.StatusUnauthorized)
	}

	token, err := loginUser.CreateLoginToken()
	if err != nil {
		return echo.NewHTTPError(http.StatusUnauthorized)
	}

	fmt.Printf("token=%s\n", token)
	res := new(LoginResponce)
	res.Token = token
	return c.JSON(http.StatusOK, res)
}

//CreateUserHandler は指定したユーザーを作成する
func CreateUserHandler(c echo.Context) error {
	req := new(CreateUserRequest)
	if err := c.Bind(req); err != nil {
		return err
	}
	fmt.Printf("request=%v\n", *req)

	loginUser := NewLoginUserFromRequest(c)
	if loginUser.AuthLevel != db.UserPermissionAdmin {
		return fmt.Errorf("ログインユーザーが管理者権限を持っていない")
	}

	user, err := db.SelectUser(req.UserName)
	if err == nil && user.ID != 0 {
		return fmt.Errorf("すでにユーザーが存在する")
	}

	err = db.InsertUser(req.UserName, req.Password, req.AuthLevel)
	if err != nil {
		return err
	}

	res := new(CreateUserResponce)
	res.Status = 0
	return c.JSON(http.StatusOK, res)
}

//DeleteUserHandler は指定したユーザーを削除する。削除するには管理者権限が必要
func DeleteUserHandler(c echo.Context) error {
	req := new(DeleteUserRequest)
	if err := c.Bind(req); err != nil {
		return err
	}
	fmt.Printf("request=%v\n", *req)

	user, err := db.SelectUser(req.UserName)
	if err != nil || user.ID == 0 {
		return fmt.Errorf("削除するユーザーが見つからない")
	}

	//トークンからユーザー名を取得
	loginUser := NewLoginUserFromRequest(c)
	if loginUser.AuthLevel != db.UserPermissionAdmin {
		return fmt.Errorf("ログインユーザーが管理者権限を持っていない")
	}
	if loginUser.UserName == req.UserName {
		return fmt.Errorf("現在ログインしているユーザーは削除できない")
	}

	//ユーザーを削除
	err = db.DeleteUser(user.ID)
	if err != nil {
		return err
	}

	//履歴も削除
	err = db.DeleteHistory(user.Name)
	if err != nil {
		return err
	}

	res := new(DeleteUserResponce)
	res.Status = 0
	return c.JSON(http.StatusOK, res)
}

//UserListHandler はユーザー一覧を取得する
func UserListHandler(c echo.Context) error {
	userList, err := db.SelectUserAll()
	if err != nil {
		return err
	}

	res := new(UserListResponce)
	res.Count = len(userList)
	userResponceList := make([]UserListUsersResponce, 0)
	for _, user := range userList {
		userResponceList = append(userResponceList, UserListUsersResponce{
			UserName:  user.Name,
			AuthLevel: user.Permission,
		})
	}
	res.Users = userResponceList
	return c.JSON(http.StatusOK, res)
}
<|endoftext|>


<commit_before>/*
// Copyright (c) 2016 Intel Corporation
//
// 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.
*/

// ciao-launcher is an ssntp agent that runs on compute and network nodes.
// Its primary purpose is to launch and manage containers and virtual
// machines.  For more information on installing and running ciao-launcher
// see https://github.com/01org/ciao/blob/master/ciao-launcher/README.md
// for more information.
//
// Introduction
//
// ciao-launcher tries to take advantage of Go's concurrency support as much as
// possible.  The intention here is that most of the work involved in launching
// and manipulating VMs is mostly self contained and IO bound and so should in
// theory lend itself well to a concurrent design.  As a consequence, ciao-launcher
// is highly concurrent and performant.  The concurrent nature
// of ciao-launcher can make it a little difficult to understand for new comers,
// so here are a few notes on its design.  ciao-launcher can be thought of as a
// collection of distinct go routines.  These notes explain what these go
// routines are for and how they communicate.
//
// Main
//
// Main is the go routine that starts when ciao-launcher is itself launched.  The code
// for this is in main.go.  It parses the command line parameters, initialises
// networking, ensures that no other instance of ciao-launcher are running and then
// starts the server go routine.  Having done all this, the main go routine waits
// for a signal, e.g., SIGTERM, from the OS to quit.  When this signal is retrieved
// it instructs all child go routines to quit and waits for their exit.  Note that
// it only waits for 1 second.  If all child go routines have failed to exit in 1
// second, ciao-launcher panics.  The panic is useful as it prints the stack trace of
// all the running go routines, so you can see which ones are blocked.  At least
// this was the intention.  The default behaviour of the go runtime has changed in
// this regard in 1.6 so a small code change is required, but you get the idea, I
// hope.
//
// The Server go routine
//
// Manages the connection to the SSNTP server and pre-processes all commands
// received from this server. The code for this go routine is also in main.go, at
// least for the time being.  ciao-Launcher
// establishes a connection to the ssntp server via the ssntp.Dial command.  This
// creates a separate go routine, managed by the ssntp library.  Any subsequent
// ssntp events that occur are handled by the ciao-launcher function CommandNotify.
// CommandNotify is called in the context of the ssntp go routine.  To avoid
// blocking this go routine, ciao-launcher parses the YAML associated with the command,
// sends a newly created internal command down a channel to the server go routine
// and returns.  The command is then processed further in the server go routine.
//
// Most commands are operations on instances, e.g., create a new instance, restart
// an instance, and are ultimately processed by a go routine dedicated to the
// particular instance to which they pertain.  These instance go routines are
// managed by another go routine called the overseer which will be discussed in
// more detail below.  Before the server go routine can forward a command to the
// appropriate go routine it needs to ask the overseer for a channel which can be
// used to communicate with the relevant instance go routine.  This is done by
// sending an ovsAddCmd or an ovsGetCmd to the overseer via the overseer channel,
// ovsCh.  ovsAddCmd is used when starting a new instance.  ovsGetCmd is used to
// process all other commands.
//
// The overseer go routine is started by the server go routine.  When the server
// go routine is asked to exit by the main go routine, i.e., main go routine
// closes the doneCh channel, the server go routine closes the channel it uses
// to communicate with the overseer.  This instructs the overseer to close,
// which it does after all the instance go routines it manages have in turn
// exited.  The server go routine waits for the overseer to exit before
// terminating.
//
// The Overseer
//
// The overseer is a go routine that serves three main purposes.
//
//  1.  It manages instance go routines that themselves manage individual vms.
//  2.  It collects statistics about the node and the VMs it hosts and
//      tranmits these periodically to the ssntp server via the STATS and
//      STATUS commands.
//  3.  It Rediscovers and reconnects to existing instances when ciao-launcher is started.
// Overseer launches new instances via the startInstance function from instance.go.
// This function starts a new go routine for that instance and returns a channel
// through which commands can be sent to the instance.  The overseer itself does
// not send commands down this channel.  It cannot as this would lead to deadlock.
// Instead it makes this channel available to the server go routine when it is
// requested via the ovsAddCmd or the ovsGetCmd commands.
//
// The overseer passes each instance go routine a reference to the a single
// channel, childDoneCh.  This channel is closed when the overseer starts shutting
// down.  Closing this channel serves as a broadcast notification to each instance
// go routine, indicating that they need to shutdown.  The overseer waits until all
// instance go routines have shut down before exiting.  This is achieved via a wait
// group called chilWg.
//
// The overseer maintains a map of information about each instance called
// instances.  The map is indexed by the uuid of instances.  It contains
// information about the instances, namely their running state and their resource
// usage.  This information is used when sending STATs and STATUS commands.
//
// Information about the instances ultimately comes from the instance go routines.
// However, these go routines cannot access the overseer's instance map directly.
// To update it they send commands down a channel, which the overseer passes to
// startInstance, for example, ovsStatsUpdateCmd or ovsStateChange.  The overseer
// processes these commands in the processCommand function.
//
// The Instance Go routines
//
// ciao-launcher maintains one go routine per instance it manages.  These go routines
// exist regardless of the state of the underlying instance, i.e., there is
// generally one go routine running per instance, regardless of whether that
// instance is pending, exited or running.
//
// The instance go routines serve 3 main purposes:
//
//  1. They accept and process commands from the server go routine down their
//     command channel.  These commands typically come from the ssntp server,
//     although there are some occasions where the commands originate from inside
//     ciao-launcher itself.
//  2. They monitor the running state of VMs.
//  3. They manage the collection of instance statistics, which they report
//     to the overseer.
//
// The nice thing about this design is that almost all instance related work can be
// performed in parallel.  Stats can be computed for one instance at the same time
// as a separate instance is being powered down.  ciao-Launcher can process any number
// of commands to start new instances in parallel.  There is no locking required
// apart from a synchronised access to the overseer map made by the server go
// routine when the command is first received and a small check related to the
// image from which the instance will be launched, in the case where instances are
// being started.  An additional synchronisation point is required for docker
// instances to ensure that the relevant docker network has been created
// before the container.
//
// Note that although commands submitted to different instances are in executed in
// parallel, the instance go routines serialise commands issue against a single
// instance.  This is necessary to avoid instance corruption.
//
// Right now the command channels that the server go routine uses to send command
// to instances are not buffered.  This might need to change as currently it could
// be possible for a SSNTP server to kill ciao-launcher's parallelism by repetively
// sending commands to the same instance over and over again.
//
// The code for the instance go routines is in instance.go.  However, the code that
// executes most of the commands has been placed in separate files named after the
// commands themselves, e.g., start.go, delete.go, restart.go.  It should be noted
// that the code in these files runs in the context of an instance go routine.
// Finally, some of the code used to process instance commands is in payloads.go.
// This is for legacy reasons and in the future this file will probably go away and
// its contents will be redistributed.  It should not be assumed as of the time of
// writing that all the code in payloads.go runs in an instance go routine.
// payloads.go needs cleaning up (https://github.com/01org/ciao/issues/10).
//
// The virtualizer
//
// The instance go routines need to talk to qemu and docker to manage their VMs
// and containers.  However, they do not do so directly.  Rather they do so via
// a virtualizer interface.
//
// The virtualizer interface is designed to isolate ciao-launcher, and in particular,
// functions that run in the instance go routine, from the underlying virtualisation
// technologies used to launch and manage VMs and containers.
// All the methods on the virtualizer interface will be called serially by the instance
// go routine.  Therefore there is no need to synchronised data between the virtualizers
// methods.
//
// qemu.go contains functions to start and stop a VM, to create a qcow2 image, to
// collect statistics about the instance running in the hypervisor, such as its
// memory and cpu usage, and to monitor the instance, i.e., to determine whether
// the VM is actually running or not.
//
// docker.go contains methods to manage docker containers.
//
// For more information about the virtualizer API, please see the comments
// in https://github.com/01org/ciao/blob/master/ciao-launcher/virtualizer.go
//
package main
<commit_msg>ciao-launcher: Update doc to say panic() will now show all goroutines<commit_after>/*
// Copyright (c) 2016 Intel Corporation
//
// 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.
*/

// ciao-launcher is an ssntp agent that runs on compute and network nodes.
// Its primary purpose is to launch and manage containers and virtual
// machines.  For more information on installing and running ciao-launcher
// see https://github.com/01org/ciao/blob/master/ciao-launcher/README.md
// for more information.
//
// Introduction
//
// ciao-launcher tries to take advantage of Go's concurrency support as much as
// possible.  The intention here is that most of the work involved in launching
// and manipulating VMs is mostly self contained and IO bound and so should in
// theory lend itself well to a concurrent design.  As a consequence, ciao-launcher
// is highly concurrent and performant.  The concurrent nature
// of ciao-launcher can make it a little difficult to understand for new comers,
// so here are a few notes on its design.  ciao-launcher can be thought of as a
// collection of distinct go routines.  These notes explain what these go
// routines are for and how they communicate.
//
// Main
//
// Main is the go routine that starts when ciao-launcher is itself launched.  The code
// for this is in main.go.  It parses the command line parameters, initialises
// networking, ensures that no other instance of ciao-launcher are running and then
// starts the server go routine.  Having done all this, the main go routine waits
// for a signal, e.g., SIGTERM, from the OS to quit.  When this signal is retrieved
// it instructs all child go routines to quit and waits for their exit.  Note that
// it only waits for 1 second.  If all child go routines have failed to exit in 1
// second, ciao-launcher panics.  The panic is useful as it prints the stack trace of
// all the running go routines, so you can see which ones are blocked.
//
// The Server go routine
//
// Manages the connection to the SSNTP server and pre-processes all commands
// received from this server. The code for this go routine is also in main.go, at
// least for the time being.  ciao-Launcher
// establishes a connection to the ssntp server via the ssntp.Dial command.  This
// creates a separate go routine, managed by the ssntp library.  Any subsequent
// ssntp events that occur are handled by the ciao-launcher function CommandNotify.
// CommandNotify is called in the context of the ssntp go routine.  To avoid
// blocking this go routine, ciao-launcher parses the YAML associated with the command,
// sends a newly created internal command down a channel to the server go routine
// and returns.  The command is then processed further in the server go routine.
//
// Most commands are operations on instances, e.g., create a new instance, restart
// an instance, and are ultimately processed by a go routine dedicated to the
// particular instance to which they pertain.  These instance go routines are
// managed by another go routine called the overseer which will be discussed in
// more detail below.  Before the server go routine can forward a command to the
// appropriate go routine it needs to ask the overseer for a channel which can be
// used to communicate with the relevant instance go routine.  This is done by
// sending an ovsAddCmd or an ovsGetCmd to the overseer via the overseer channel,
// ovsCh.  ovsAddCmd is used when starting a new instance.  ovsGetCmd is used to
// process all other commands.
//
// The overseer go routine is started by the server go routine.  When the server
// go routine is asked to exit by the main go routine, i.e., main go routine
// closes the doneCh channel, the server go routine closes the channel it uses
// to communicate with the overseer.  This instructs the overseer to close,
// which it does after all the instance go routines it manages have in turn
// exited.  The server go routine waits for the overseer to exit before
// terminating.
//
// The Overseer
//
// The overseer is a go routine that serves three main purposes.
//
//  1.  It manages instance go routines that themselves manage individual vms.
//  2.  It collects statistics about the node and the VMs it hosts and
//      tranmits these periodically to the ssntp server via the STATS and
//      STATUS commands.
//  3.  It Rediscovers and reconnects to existing instances when ciao-launcher is started.
// Overseer launches new instances via the startInstance function from instance.go.
// This function starts a new go routine for that instance and returns a channel
// through which commands can be sent to the instance.  The overseer itself does
// not send commands down this channel.  It cannot as this would lead to deadlock.
// Instead it makes this channel available to the server go routine when it is
// requested via the ovsAddCmd or the ovsGetCmd commands.
//
// The overseer passes each instance go routine a reference to the a single
// channel, childDoneCh.  This channel is closed when the overseer starts shutting
// down.  Closing this channel serves as a broadcast notification to each instance
// go routine, indicating that they need to shutdown.  The overseer waits until all
// instance go routines have shut down before exiting.  This is achieved via a wait
// group called chilWg.
//
// The overseer maintains a map of information about each instance called
// instances.  The map is indexed by the uuid of instances.  It contains
// information about the instances, namely their running state and their resource
// usage.  This information is used when sending STATs and STATUS commands.
//
// Information about the instances ultimately comes from the instance go routines.
// However, these go routines cannot access the overseer's instance map directly.
// To update it they send commands down a channel, which the overseer passes to
// startInstance, for example, ovsStatsUpdateCmd or ovsStateChange.  The overseer
// processes these commands in the processCommand function.
//
// The Instance Go routines
//
// ciao-launcher maintains one go routine per instance it manages.  These go routines
// exist regardless of the state of the underlying instance, i.e., there is
// generally one go routine running per instance, regardless of whether that
// instance is pending, exited or running.
//
// The instance go routines serve 3 main purposes:
//
//  1. They accept and process commands from the server go routine down their
//     command channel.  These commands typically come from the ssntp server,
//     although there are some occasions where the commands originate from inside
//     ciao-launcher itself.
//  2. They monitor the running state of VMs.
//  3. They manage the collection of instance statistics, which they report
//     to the overseer.
//
// The nice thing about this design is that almost all instance related work can be
// performed in parallel.  Stats can be computed for one instance at the same time
// as a separate instance is being powered down.  ciao-Launcher can process any number
// of commands to start new instances in parallel.  There is no locking required
// apart from a synchronised access to the overseer map made by the server go
// routine when the command is first received and a small check related to the
// image from which the instance will be launched, in the case where instances are
// being started.  An additional synchronisation point is required for docker
// instances to ensure that the relevant docker network has been created
// before the container.
//
// Note that although commands submitted to different instances are in executed in
// parallel, the instance go routines serialise commands issue against a single
// instance.  This is necessary to avoid instance corruption.
//
// Right now the command channels that the server go routine uses to send command
// to instances are not buffered.  This might need to change as currently it could
// be possible for a SSNTP server to kill ciao-launcher's parallelism by repetively
// sending commands to the same instance over and over again.
//
// The code for the instance go routines is in instance.go.  However, the code that
// executes most of the commands has been placed in separate files named after the
// commands themselves, e.g., start.go, delete.go, restart.go.  It should be noted
// that the code in these files runs in the context of an instance go routine.
// Finally, some of the code used to process instance commands is in payloads.go.
// This is for legacy reasons and in the future this file will probably go away and
// its contents will be redistributed.  It should not be assumed as of the time of
// writing that all the code in payloads.go runs in an instance go routine.
// payloads.go needs cleaning up (https://github.com/01org/ciao/issues/10).
//
// The virtualizer
//
// The instance go routines need to talk to qemu and docker to manage their VMs
// and containers.  However, they do not do so directly.  Rather they do so via
// a virtualizer interface.
//
// The virtualizer interface is designed to isolate ciao-launcher, and in particular,
// functions that run in the instance go routine, from the underlying virtualisation
// technologies used to launch and manage VMs and containers.
// All the methods on the virtualizer interface will be called serially by the instance
// go routine.  Therefore there is no need to synchronised data between the virtualizers
// methods.
//
// qemu.go contains functions to start and stop a VM, to create a qcow2 image, to
// collect statistics about the instance running in the hypervisor, such as its
// memory and cpu usage, and to monitor the instance, i.e., to determine whether
// the VM is actually running or not.
//
// docker.go contains methods to manage docker containers.
//
// For more information about the virtualizer API, please see the comments
// in https://github.com/01org/ciao/blob/master/ciao-launcher/virtualizer.go
//
package main
<|endoftext|>


<commit_before>package events

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/byuoitav/event-router-microservice/eventinfrastructure"
	"github.com/gorilla/websocket"
	"github.com/xuther/go-message-router/common"
	"github.com/xuther/go-message-router/subscriber"
)

var Sub subscriber.Subscriber

type filter func(common.Message) eventinfrastructure.EventInfo

type ClientManager struct {
	clients    map[*Client]bool
	Broadcast  chan common.Message
	register   chan *Client
	unregister chan *Client
}

type Client struct {
	socket *websocket.Conn
	send   chan []byte
}

var Manager = &ClientManager{
	Broadcast:  make(chan common.Message, 100),
	register:   make(chan *Client),
	unregister: make(chan *Client),
	clients:    make(map[*Client]bool),
}

func SubInit() {
	var err error
	Sub, err = subscriber.NewSubscriber(10)
	if err != nil {
		log.Fatalf("Could not create a subscriber. Error: %v\n", err.Error())
	}

	Sub.Subscribe("localhost:7003", []string{eventinfrastructure.UI})
	log.Printf("[Routing] Subscribed to %s events", eventinfrastructure.UI)

	go Manager.Start(UIFilter)
	go SubListen()
}

func (manager *ClientManager) Start(f filter) {
	for {
		select {
		case conn := <-manager.register:
			log.Printf("[Socket] Registering connection")
			manager.clients[conn] = true

		case conn := <-manager.unregister:
			log.Printf("[Socket] Unregistering connection")
			if _, ok := manager.clients[conn]; ok {
				close(conn.send)
				delete(manager.clients, conn)
			}

		case event := <-manager.Broadcast:
			e := f(event)
			toSend, err := json.Marshal(&e)
			if err != nil {
				log.Printf("error: %s", err.Error())
				continue
			}
			log.Printf("[Socket] Sending event: %s", toSend)
			for conn := range manager.clients {
				select {
				case conn.send <- toSend:
				default:
					close(conn.send)
					delete(manager.clients, conn)
				}
			}
		}
	}
}

func (c *Client) write() {
	for {
		select {
		case message, ok := <-c.send:
			log.Printf("[Client] Writing event to client %v", c.socket)
			if !ok {
				log.Printf("[Client] client send channel closed")
				return
			}

			c.socket.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func (c *Client) read() {
	for {
		if _, _, err := c.socket.NextReader(); err != nil {
			log.Printf("[Client] Unregistering connection")
			Manager.unregister <- c
			break
		}
	}
}

func StartWebClient(res http.ResponseWriter, req *http.Request) {
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)
	if err != nil {
		http.NotFound(res, req)
		return
	}

	client := &Client{socket: conn, send: make(chan []byte)}

	Manager.register <- client

	go client.write()
	go client.read()
}

func UIFilter(event common.Message) eventinfrastructure.EventInfo {
	var e eventinfrastructure.Event
	err := json.Unmarshal(event.MessageBody, &e)
	if err != nil {
		log.Printf("error: %v", err.Error())
		return eventinfrastructure.EventInfo{}
	}

	return e.Event
}

func SubListen() {
	log.Printf("Subscriber is listening for events")

	for {
		message := Sub.Read()
		Manager.Broadcast <- message
	}
}
<commit_msg>fixed unsubscribing<commit_after>package events

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/byuoitav/event-router-microservice/eventinfrastructure"
	"github.com/gorilla/websocket"
	"github.com/xuther/go-message-router/common"
	"github.com/xuther/go-message-router/subscriber"
)

var Sub subscriber.Subscriber

type filter func(common.Message) eventinfrastructure.EventInfo

type ClientManager struct {
	clients    map[*Client]bool
	Broadcast  chan common.Message
	register   chan *Client
	unregister chan *Client
}

type Client struct {
	socket *websocket.Conn
	send   chan []byte
}

var Manager = &ClientManager{
	Broadcast:  make(chan common.Message, 100),
	register:   make(chan *Client),
	unregister: make(chan *Client),
	clients:    make(map[*Client]bool),
}

func SubInit() {
	var err error
	Sub, err = subscriber.NewSubscriber(10)
	if err != nil {
		log.Fatalf("Could not create a subscriber. Error: %v\n", err.Error())
	}

	Sub.Subscribe("localhost:7003", []string{eventinfrastructure.UI})
	log.Printf("[Routing] Subscribed to %s events", eventinfrastructure.UI)

	go Manager.Start(UIFilter)
	go SubListen()
}

func (manager *ClientManager) Start(f filter) {
	for {
		select {
		case conn := <-manager.register:
			log.Printf("[Socket] Registering connection")
			manager.clients[conn] = true

		case conn := <-manager.unregister:
			log.Printf("[Socket] Unregistering connection")
			if _, ok := manager.clients[conn]; ok {
				close(conn.send)
				delete(manager.clients, conn)
			}

		case event := <-manager.Broadcast:
			e := f(event)
			toSend, err := json.Marshal(&e)
			if err != nil {
				log.Printf("error: %s", err.Error())
				continue
			}
			log.Printf("[Socket] Sending event %s", toSend)
			for conn := range manager.clients {
				select {
				case conn.send <- toSend:
				default:
					close(conn.send)
					delete(manager.clients, conn)
				}
			}
		}
	}
}

func (c *Client) write() {
	for {
		select {
		case message, ok := <-c.send:
			if !ok {
				log.Printf("[Client] client send channel closed")
				return
			}

			log.Printf("[Client] Writing event to client")
			c.socket.WriteMessage(websocket.TextMessage, message)
		}
	}
}

func (c *Client) read() {
	for {
		if _, _, err := c.socket.NextReader(); err != nil {
			log.Printf("[Client] Unregistering connection")
			Manager.unregister <- c
			break
		}
	}
}

func StartWebClient(res http.ResponseWriter, req *http.Request) {
	conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)
	if err != nil {
		http.NotFound(res, req)
		return
	}

	client := &Client{socket: conn, send: make(chan []byte)}

	Manager.register <- client

	go client.write()
	go client.read()
}

func UIFilter(event common.Message) eventinfrastructure.EventInfo {
	var e eventinfrastructure.Event
	err := json.Unmarshal(event.MessageBody, &e)
	if err != nil {
		log.Printf("error: %v", err.Error())
		return eventinfrastructure.EventInfo{}
	}

	return e.Event
}

func SubListen() {
	log.Printf("Subscriber is listening for events")

	for {
		message := Sub.Read()
		Manager.Broadcast <- message
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math"
	"time"

	"../sensors"

	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/all"
	// "github.com/kidoman/embd/sensor/bmp180"
	"github.com/westphae/goflying/ahrs"
	"github.com/westphae/goflying/ahrsweb"
)

var (
	i2cbus           embd.I2CBus
	myPressureReader sensors.PressureReader
	myIMUReader      sensors.IMUReader
)

func initI2CSensors() {
	i2cbus = embd.NewI2CBus(1)

	go pollSensors()
	go sensorAttitudeSender()
}

func pollSensors() {
	timer := time.NewTicker(4 * time.Second)
	for {
		<-timer.C

		// If it's not currently connected, try connecting to pressure sensor
		if globalSettings.Sensors_Enabled && !globalStatus.PressureSensorConnected {
			globalStatus.PressureSensorConnected = initPressureSensor() // I2C temperature and pressure altitude.
		}

		// If it's not currently connected, try connecting to IMU
		if globalSettings.Sensors_Enabled && !globalStatus.IMUConnected {
			globalStatus.IMUConnected = initIMU() // I2C accel/gyro/mag.
		}
	}
}

func initPressureSensor() (ok bool) {
	bmp, err := sensors.NewBMP280(&i2cbus, 100*time.Millisecond)
	if err == nil {
		myPressureReader = bmp
		go tempAndPressureSender()
		log.Println("AHRS Info: Successfully initialized BMP280")
		return true
	}

	// TODO westphae: make bmp180.go to fit bmp interface
	//for i := 0; i < 5; i++ {
	//	myBMPX80 = bmp180.New(i2cbus)
	//	_, err := myBMPX80.Temperature() // Test to see if it works, since bmp180.New doesn't return err
	//	if err != nil {
	//		time.Sleep(250 * time.Millisecond)
	//	} else {
	//		globalStatus.PressureSensorConnected = true
	//		log.Println("AHRS Info: Successfully initialized BMP180")
	//		return nil
	//	}
	//}

	log.Println("AHRS Info: couldn't initialize BMP280 or BMP180")
	return false
}

func initIMU() (ok bool) {
	log.Println("AHRS Info: attempting to connect to MPU9250")
	imu, err := sensors.NewMPU9250()
	if err == nil {
		myIMUReader = imu
		time.Sleep(200 * time.Millisecond)
		log.Println("AHRS Info: Successfully connected MPU9250, running calibration")
		myIMUReader.Calibrate(1, 5)
		log.Println("AHRS Info: Successfully calibrated MPU9250")
		return true
	}

	// TODO westphae: try to connect to MPU9150

	log.Println("AHRS Error: couldn't initialize MPU9250 or MPU9150")
	return false
}

func tempAndPressureSender() {
	var (
		temp     float64
		press    float64
		altLast  float64
		altitude float64
		err      error
		dt       float64 = 0.1
	)

	// Initialize variables for rate of climb calc
	u := 5 / (5 + float64(dt)) // Use 5 sec decay time for rate of climb, slightly faster than typical VSI
	if press, err = myPressureReader.Pressure(); err != nil {
		log.Printf("AHRS Error: Couldn't read pressure from sensor: %s", err)
		myPressureReader.Close()
		globalStatus.PressureSensorConnected = false // Try reconnecting a little later
	}
	altLast = CalcAltitude(press)

	timer := time.NewTicker(time.Duration(1000*dt) * time.Millisecond)
	for globalSettings.Sensors_Enabled && globalStatus.PressureSensorConnected {
		<-timer.C

		// Read temperature and pressure altitude.
		temp, err = myPressureReader.Temperature()
		if err != nil {
			log.Printf("AHRS Error: Couldn't read temperature from sensor: %s", err)
		}
		press, err = myPressureReader.Pressure()
		if err != nil {
			log.Printf("AHRS Error: Couldn't read pressure from sensor: %s", err)
			myPressureReader.Close()
			globalStatus.PressureSensorConnected = false // Try reconnecting a little later
			break
		}

		// Update the Situation data.
		mySituation.mu_Pressure.Lock()
		mySituation.LastTempPressTime = stratuxClock.Time
		mySituation.Temp = temp
		altitude = CalcAltitude(press)
		mySituation.Pressure_alt = altitude
		// Assuming timer is reasonably accurate, use a regular ewma
		mySituation.RateOfClimb = u*mySituation.RateOfClimb + (1-u)*(altitude-altLast)/(float64(dt)/60)
		mySituation.mu_Pressure.Unlock()
		altLast = altitude
	}
}

func sensorAttitudeSender() {
	log.Println("AHRS Info: Setting up sensorAttitudeSender")
	var (
		roll, pitch, heading                              float64
		t                                                 time.Time
		s                                                 ahrs.AHRSProvider
		m                                                 *ahrs.Measurement
		bx, by, bz, ax, ay, az, mx, my, mz                float64
		mpuError, magError                                error
		headingMag, slipSkid, turnRate, gLoad             float64
		errHeadingMag, errSlipSkid, errTurnRate, errGLoad error
	)
	m = ahrs.NewMeasurement()

	//TODO westphae: remove this logging when finished testing, or make it optional in settings
	logger := ahrs.NewSensorLogger(fmt.Sprintf("/var/log/sensors_%s.csv", time.Now().Format("20060102_150405")),
		"T", "TS", "A1", "A2", "A3", "H1", "H2", "H3", "M1", "M2", "M3", "TW", "W1", "W2", "W3", "TA", "Alt",
		"pitch", "roll", "heading", "mag_heading", "slip_skid", "turn_rate", "g_load", "T_Attitude")
	defer logger.Close()

	ahrswebListener, err := ahrsweb.NewKalmanListener()
	if err != nil {
		log.Printf("AHRS Error: couldn't start ahrswebListener: %s\n", err.Error())
	} else {
		defer ahrswebListener.Close()
	}

	// Need a 10Hz sampling freq
	timer := time.NewTicker(100 * time.Millisecond) // ~10Hz update.
	for {
		<-timer.C
		for (globalSettings.Sensors_Enabled && globalStatus.IMUConnected) {
			<-timer.C
			t = stratuxClock.Time
			m.T = float64(t.UnixNano() / 1000) / 1e6

			_, bx, by, bz, ax, ay, az, mx, my, mz, mpuError, magError = myIMUReader.ReadRaw()
			//TODO westphae: allow user configuration of this mapping from a file, plus UI modification
			//m.B1, m.B2, m.B3 = +by, -bx, +bz // This is how the RY83XAI is wired up
			//m.A1, m.A2, m.A3 = -ay, +ax, -az // This is how the RY83XAI is wired up
			m.B1, m.B2, m.B3 = -bx, +by, -bz // This is how the OpenFlightBox board is wired up
			m.A1, m.A2, m.A3 = -ay, +ax, +az // This is how the OpenFlightBox board is wired up
			m.M1, m.M2, m.M3 = +mx, +my, +mz
			m.SValid = mpuError == nil
			m.MValid = magError == nil
			if mpuError != nil {
				log.Printf("AHRS Gyro/Accel Error: %s\n", mpuError)
				myIMUReader.Close()
				globalStatus.IMUConnected = false
			}
			if magError != nil {
				log.Printf("AHRS Magnetometer Error, not using for this run: %s\n", magError)
				m.MValid = false
				// Don't necessarily disconnect here, unless AHRSProvider deeply depends on magnetometer
			}

			m.WValid = t.Sub(mySituation.LastGroundTrackTime) < 500*time.Millisecond
			if m.WValid {
				m.W1 = mySituation.GroundSpeed * math.Sin(float64(mySituation.TrueCourse) * ahrs.Deg)
				m.W2 = mySituation.GroundSpeed * math.Cos(float64(mySituation.TrueCourse) * ahrs.Deg)
				if globalStatus.PressureSensorConnected {
					m.W3 = mySituation.RateOfClimb * 60 / 6076.12
				} else {
					m.W3 = float64(mySituation.GPSVertVel) * 3600 / 6076.12
				}
			}

			// Run the AHRS calcs
			if s == nil { // s is nil if we should (re-)initialize the Kalman state
				log.Println("AHRS Info: initializing new simple AHRS")
				s = ahrs.InitializeSimple(m, "")
			}
			s.Compute(m)

			// Debugging server:
			if ahrswebListener != nil {
				ahrswebListener.Send(s.GetState(), m)
			}

			// If we have valid AHRS info, then send
			if s.Valid() {
				mySituation.mu_Attitude.Lock()

				roll, pitch, heading = s.CalcRollPitchHeading()
				mySituation.Roll = roll / ahrs.Deg
				mySituation.Pitch = pitch / ahrs.Deg
				mySituation.Gyro_heading = heading / ahrs.Deg

				if headingMag, errHeadingMag = myIMUReader.MagHeading(); errHeadingMag != nil {
					log.Printf("AHRS MPU Error: %s\n", errHeadingMag.Error())
				} else {
					mySituation.Mag_heading = headingMag
				}

				if slipSkid, errSlipSkid = myIMUReader.SlipSkid(); errSlipSkid != nil {
					log.Printf("AHRS MPU Error: %s\n", errSlipSkid.Error())
				} else {
					mySituation.SlipSkid = slipSkid
				}

				if turnRate, errTurnRate = myIMUReader.RateOfTurn(); errTurnRate != nil {
					log.Printf("AHRS MPU Error: %s\n", errTurnRate.Error())
				} else {
					mySituation.RateOfTurn = turnRate
				}

				if gLoad, errGLoad = myIMUReader.GLoad(); errGLoad != nil {
					log.Printf("AHRS MPU Error: %s\n", errGLoad.Error())
				} else {
					mySituation.GLoad = gLoad
				}

				mySituation.LastAttitudeTime = t
				mySituation.mu_Attitude.Unlock()

				// makeFFAHRSSimReport() // simultaneous use of GDL90 and FFSIM not supported in FF 7.5.1 or later. Function definition will be kept for AHRS debugging and future workarounds.
			} else {
				s = nil
				mySituation.LastAttitudeTime = time.Time{}
			}

			makeAHRSGDL90Report() // Send whether or not valid - the function will invalidate the values as appropriate

			logger.Log(
				float64(t.UnixNano() / 1000)/1e6,
				m.T, m.A1, m.A2, m.A3, m.B1, m.B2, m.B3, m.M1, m.M2, m.M3,
				float64(mySituation.LastGroundTrackTime.UnixNano() / 1000)/1e6, m.W1, m.W2, m.W3,
				float64(mySituation.LastTempPressTime.UnixNano() / 1000)/1e6, mySituation.Pressure_alt,
				pitch/ahrs.Deg, roll/ahrs.Deg, heading/ahrs.Deg, headingMag, slipSkid, turnRate, gLoad,
				float64(mySituation.LastAttitudeTime.UnixNano() / 1000)/1e6)
		}
	}
}
<commit_msg>Make pressure reader work again.<commit_after>package main

import (
	"fmt"
	"log"
	"math"
	"time"

	"../sensors"

	"github.com/kidoman/embd"
	_ "github.com/kidoman/embd/host/all"
	// "github.com/kidoman/embd/sensor/bmp180"
	"github.com/westphae/goflying/ahrs"
	"github.com/westphae/goflying/ahrsweb"
)

var (
	i2cbus           embd.I2CBus
	myPressureReader sensors.PressureReader
	myIMUReader      sensors.IMUReader
)

func initI2CSensors() {
	i2cbus = embd.NewI2CBus(1)

	go pollSensors()
	go sensorAttitudeSender()
}

func pollSensors() {
	timer := time.NewTicker(4 * time.Second)
	for {
		<-timer.C

		// If it's not currently connected, try connecting to pressure sensor
		if globalSettings.Sensors_Enabled && !globalStatus.PressureSensorConnected {
			globalStatus.PressureSensorConnected = initPressureSensor() // I2C temperature and pressure altitude.
			go tempAndPressureSender()
		}

		// If it's not currently connected, try connecting to IMU
		if globalSettings.Sensors_Enabled && !globalStatus.IMUConnected {
			globalStatus.IMUConnected = initIMU() // I2C accel/gyro/mag.
		}
	}
}

func initPressureSensor() (ok bool) {
	bmp, err := sensors.NewBMP280(&i2cbus, 100*time.Millisecond)
	if err == nil {
		myPressureReader = bmp
		log.Println("AHRS Info: Successfully initialized BMP280")
		return true
	}

	// TODO westphae: make bmp180.go to fit bmp interface
	//for i := 0; i < 5; i++ {
	//	myBMPX80 = bmp180.New(i2cbus)
	//	_, err := myBMPX80.Temperature() // Test to see if it works, since bmp180.New doesn't return err
	//	if err != nil {
	//		time.Sleep(250 * time.Millisecond)
	//	} else {
	//		globalStatus.PressureSensorConnected = true
	//		log.Println("AHRS Info: Successfully initialized BMP180")
	//		return nil
	//	}
	//}

	log.Println("AHRS Info: couldn't initialize BMP280 or BMP180")
	return false
}

func initIMU() (ok bool) {
	log.Println("AHRS Info: attempting to connect to MPU9250")
	imu, err := sensors.NewMPU9250()
	if err == nil {
		myIMUReader = imu
		time.Sleep(200 * time.Millisecond)
		log.Println("AHRS Info: Successfully connected MPU9250, running calibration")
		myIMUReader.Calibrate(1, 5)
		log.Println("AHRS Info: Successfully calibrated MPU9250")
		return true
	}

	// TODO westphae: try to connect to MPU9150

	log.Println("AHRS Error: couldn't initialize MPU9250 or MPU9150")
	return false
}

func tempAndPressureSender() {
	var (
		temp     float64
		press    float64
		altLast  float64
		altitude float64
		err      error
		dt       float64 = 0.1
	)

	// Initialize variables for rate of climb calc
	u := 5 / (5 + float64(dt)) // Use 5 sec decay time for rate of climb, slightly faster than typical VSI
	if press, err = myPressureReader.Pressure(); err != nil {
		log.Printf("AHRS Error: Couldn't read pressure from sensor: %s", err)
		myPressureReader.Close()
		globalStatus.PressureSensorConnected = false // Try reconnecting a little later
	}
	altLast = CalcAltitude(press)

	timer := time.NewTicker(time.Duration(1000*dt) * time.Millisecond)
	for globalSettings.Sensors_Enabled && globalStatus.PressureSensorConnected {
		<-timer.C

		// Read temperature and pressure altitude.
		temp, err = myPressureReader.Temperature()
		if err != nil {
			log.Printf("AHRS Error: Couldn't read temperature from sensor: %s", err)
		}
		press, err = myPressureReader.Pressure()
		if err != nil {
			log.Printf("AHRS Error: Couldn't read pressure from sensor: %s", err)
			myPressureReader.Close()
			globalStatus.PressureSensorConnected = false // Try reconnecting a little later
			break
		}

		// Update the Situation data.
		mySituation.mu_Pressure.Lock()
		mySituation.LastTempPressTime = stratuxClock.Time
		mySituation.Temp = temp
		altitude = CalcAltitude(press)
		mySituation.Pressure_alt = altitude
		// Assuming timer is reasonably accurate, use a regular ewma
		mySituation.RateOfClimb = u*mySituation.RateOfClimb + (1-u)*(altitude-altLast)/(float64(dt)/60)
		mySituation.mu_Pressure.Unlock()
		altLast = altitude
	}
}

func sensorAttitudeSender() {
	log.Println("AHRS Info: Setting up sensorAttitudeSender")
	var (
		roll, pitch, heading                              float64
		t                                                 time.Time
		s                                                 ahrs.AHRSProvider
		m                                                 *ahrs.Measurement
		bx, by, bz, ax, ay, az, mx, my, mz                float64
		mpuError, magError                                error
		headingMag, slipSkid, turnRate, gLoad             float64
		errHeadingMag, errSlipSkid, errTurnRate, errGLoad error
	)
	m = ahrs.NewMeasurement()

	//TODO westphae: remove this logging when finished testing, or make it optional in settings
	logger := ahrs.NewSensorLogger(fmt.Sprintf("/var/log/sensors_%s.csv", time.Now().Format("20060102_150405")),
		"T", "TS", "A1", "A2", "A3", "H1", "H2", "H3", "M1", "M2", "M3", "TW", "W1", "W2", "W3", "TA", "Alt",
		"pitch", "roll", "heading", "mag_heading", "slip_skid", "turn_rate", "g_load", "T_Attitude")
	defer logger.Close()

	ahrswebListener, err := ahrsweb.NewKalmanListener()
	if err != nil {
		log.Printf("AHRS Error: couldn't start ahrswebListener: %s\n", err.Error())
	} else {
		defer ahrswebListener.Close()
	}

	// Need a 10Hz sampling freq
	timer := time.NewTicker(100 * time.Millisecond) // ~10Hz update.
	for {
		<-timer.C
		for (globalSettings.Sensors_Enabled && globalStatus.IMUConnected) {
			<-timer.C
			t = stratuxClock.Time
			m.T = float64(t.UnixNano() / 1000) / 1e6

			_, bx, by, bz, ax, ay, az, mx, my, mz, mpuError, magError = myIMUReader.ReadRaw()
			//TODO westphae: allow user configuration of this mapping from a file, plus UI modification
			//m.B1, m.B2, m.B3 = +by, -bx, +bz // This is how the RY83XAI is wired up
			//m.A1, m.A2, m.A3 = -ay, +ax, -az // This is how the RY83XAI is wired up
			m.B1, m.B2, m.B3 = -bx, +by, -bz // This is how the OpenFlightBox board is wired up
			m.A1, m.A2, m.A3 = -ay, +ax, +az // This is how the OpenFlightBox board is wired up
			m.M1, m.M2, m.M3 = +mx, +my, +mz
			m.SValid = mpuError == nil
			m.MValid = magError == nil
			if mpuError != nil {
				log.Printf("AHRS Gyro/Accel Error: %s\n", mpuError)
				myIMUReader.Close()
				globalStatus.IMUConnected = false
			}
			if magError != nil {
				log.Printf("AHRS Magnetometer Error, not using for this run: %s\n", magError)
				m.MValid = false
				// Don't necessarily disconnect here, unless AHRSProvider deeply depends on magnetometer
			}

			m.WValid = t.Sub(mySituation.LastGroundTrackTime) < 500*time.Millisecond
			if m.WValid {
				m.W1 = mySituation.GroundSpeed * math.Sin(float64(mySituation.TrueCourse) * ahrs.Deg)
				m.W2 = mySituation.GroundSpeed * math.Cos(float64(mySituation.TrueCourse) * ahrs.Deg)
				if globalStatus.PressureSensorConnected {
					m.W3 = mySituation.RateOfClimb * 60 / 6076.12
				} else {
					m.W3 = float64(mySituation.GPSVertVel) * 3600 / 6076.12
				}
			}

			// Run the AHRS calcs
			if s == nil { // s is nil if we should (re-)initialize the Kalman state
				log.Println("AHRS Info: initializing new simple AHRS")
				s = ahrs.InitializeSimple(m, "")
			}
			s.Compute(m)

			// Debugging server:
			if ahrswebListener != nil {
				ahrswebListener.Send(s.GetState(), m)
			}

			// If we have valid AHRS info, then send
			if s.Valid() {
				mySituation.mu_Attitude.Lock()

				roll, pitch, heading = s.CalcRollPitchHeading()
				mySituation.Roll = roll / ahrs.Deg
				mySituation.Pitch = pitch / ahrs.Deg
				mySituation.Gyro_heading = heading / ahrs.Deg

				if headingMag, errHeadingMag = myIMUReader.MagHeading(); errHeadingMag != nil {
					log.Printf("AHRS MPU Error: %s\n", errHeadingMag.Error())
				} else {
					mySituation.Mag_heading = headingMag
				}

				if slipSkid, errSlipSkid = myIMUReader.SlipSkid(); errSlipSkid != nil {
					log.Printf("AHRS MPU Error: %s\n", errSlipSkid.Error())
				} else {
					mySituation.SlipSkid = slipSkid
				}

				if turnRate, errTurnRate = myIMUReader.RateOfTurn(); errTurnRate != nil {
					log.Printf("AHRS MPU Error: %s\n", errTurnRate.Error())
				} else {
					mySituation.RateOfTurn = turnRate
				}

				if gLoad, errGLoad = myIMUReader.GLoad(); errGLoad != nil {
					log.Printf("AHRS MPU Error: %s\n", errGLoad.Error())
				} else {
					mySituation.GLoad = gLoad
				}

				mySituation.LastAttitudeTime = t
				mySituation.mu_Attitude.Unlock()

				// makeFFAHRSSimReport() // simultaneous use of GDL90 and FFSIM not supported in FF 7.5.1 or later. Function definition will be kept for AHRS debugging and future workarounds.
			} else {
				s = nil
				mySituation.LastAttitudeTime = time.Time{}
			}

			makeAHRSGDL90Report() // Send whether or not valid - the function will invalidate the values as appropriate

			logger.Log(
				float64(t.UnixNano() / 1000)/1e6,
				m.T, m.A1, m.A2, m.A3, m.B1, m.B2, m.B3, m.M1, m.M2, m.M3,
				float64(mySituation.LastGroundTrackTime.UnixNano() / 1000)/1e6, m.W1, m.W2, m.W3,
				float64(mySituation.LastTempPressTime.UnixNano() / 1000)/1e6, mySituation.Pressure_alt,
				pitch/ahrs.Deg, roll/ahrs.Deg, heading/ahrs.Deg, headingMag, slipSkid, turnRate, gLoad,
				float64(mySituation.LastAttitudeTime.UnixNano() / 1000)/1e6)
		}
	}
}
<|endoftext|>


<commit_before>package mains

import (
	"context"
	"fmt"
	"io"
	"os"
	"runtime"

	"github.com/mattn/go-isatty"
	"github.com/zetamatta/go-getch"

	"github.com/zetamatta/nyagos/alias"
	"github.com/zetamatta/nyagos/commands"
	"github.com/zetamatta/nyagos/completion"
	"github.com/zetamatta/nyagos/dos"
	"github.com/zetamatta/nyagos/history"
	"github.com/zetamatta/nyagos/readline"
	"github.com/zetamatta/nyagos/shell"
)

var default_history *history.Container

type MainStream struct {
	shell.Stream
}

func (this *MainStream) ReadLine(ctx context.Context) (context.Context, string, error) {
	ctx = context.WithValue(ctx, history.PackageId, default_history)
	ctx, line, err := this.Stream.ReadLine(ctx)
	if err != nil {
		return ctx, "", err
	}
	return ctx, line, nil
}

type ScriptEngineForOptionImpl struct{}

func (this *ScriptEngineForOptionImpl) SetArg(args []string) {}

func (this *ScriptEngineForOptionImpl) RunFile(fname string) ([]byte, error) {
	println("Script is not supported.")
	return nil, nil
}

func (this *ScriptEngineForOptionImpl) RunString(code string) error {
	println("Script is not supported.")
	return nil
}

func Main() error {
	shell.SetHook(func(ctx context.Context, it *shell.Cmd) (int, bool, error) {
		rc, done, err := commands.Exec(ctx, it)
		return rc, done, err
	})
	completion.AppendCommandLister(commands.AllNames)
	completion.AppendCommandLister(alias.AllNames)

	dos.CoInitializeEx(0, dos.COINIT_MULTITHREADED)
	defer dos.CoUninitialize()

	getch.DisableCtrlC()

	alias.Init()

	sh := shell.New()

	langEngine := func(fname string) ([]byte, error) {
		return nil, nil
	}
	shellEngine := func(fname string) error {
		fd, err := os.Open(fname)
		if err != nil {
			return err
		}
		stream1 := NewCmdStreamFile(fd)
		_, err = sh.Loop(stream1)
		fd.Close()
		if err == io.EOF {
			return nil
		} else {
			return err
		}
	}

	if !isatty.IsTerminal(os.Stdin.Fd()) {
		SilentMode = true
	}

	if !OptionNorc {
		if !SilentMode {
			fmt.Printf("Nihongo Yet Another GOing Shell %s-%s by %s\n",
				VersionOrStamp(),
				runtime.GOARCH,
				runtime.Version())
			fmt.Println("(c) 2014-2018 NYAOS.ORG <http://www.nyaos.org>")
		}
		if err := LoadScripts(shellEngine, langEngine); err != nil {
			fmt.Fprintln(os.Stderr, err.Error())
		}
	}

	backupHistory := default_history
	defer func() {
		default_history = backupHistory
	}()

	var stream1 shell.Stream
	if isatty.IsTerminal(os.Stdin.Fd()) {
		constream := NewCmdStreamConsole(
			func() (int, error) {
				fmt.Fprint(readline.Console,
					Format2Prompt(os.Getenv("PROMPT")))
				return 0, nil
			})
		stream1 = constream
		default_history = constream.History
	} else {
		stream1 = NewCmdStreamFile(os.Stdin)
	}

	for {
		_, err := sh.Loop(&MainStream{stream1})
		if err == io.EOF {
			return err
		}
		if err != nil {
			fmt.Println(err.Error())
		}
	}
}
<commit_msg>Fix: nyagos without lua forgot option parser.<commit_after>package mains

import (
	"context"
	"fmt"
	"io"
	"os"
	"runtime"

	"github.com/mattn/go-isatty"
	"github.com/zetamatta/go-getch"

	"github.com/zetamatta/nyagos/alias"
	"github.com/zetamatta/nyagos/commands"
	"github.com/zetamatta/nyagos/completion"
	"github.com/zetamatta/nyagos/dos"
	"github.com/zetamatta/nyagos/history"
	"github.com/zetamatta/nyagos/readline"
	"github.com/zetamatta/nyagos/shell"
)

var default_history *history.Container

type MainStream struct {
	shell.Stream
}

func (this *MainStream) ReadLine(ctx context.Context) (context.Context, string, error) {
	ctx = context.WithValue(ctx, history.PackageId, default_history)
	ctx, line, err := this.Stream.ReadLine(ctx)
	if err != nil {
		return ctx, "", err
	}
	return ctx, line, nil
}

type ScriptEngineForOptionImpl struct{}

func (this *ScriptEngineForOptionImpl) SetArg(args []string) {}

func (this *ScriptEngineForOptionImpl) RunFile(fname string) ([]byte, error) {
	println("Script is not supported.")
	return nil, nil
}

func (this *ScriptEngineForOptionImpl) RunString(code string) error {
	println("Script is not supported.")
	return nil
}

func Main() error {
	shell.SetHook(func(ctx context.Context, it *shell.Cmd) (int, bool, error) {
		rc, done, err := commands.Exec(ctx, it)
		return rc, done, err
	})
	completion.AppendCommandLister(commands.AllNames)
	completion.AppendCommandLister(alias.AllNames)

	dos.CoInitializeEx(0, dos.COINIT_MULTITHREADED)
	defer dos.CoUninitialize()

	getch.DisableCtrlC()

	alias.Init()

	sh := shell.New()

	langEngine := func(fname string) ([]byte, error) {
		return nil, nil
	}
	shellEngine := func(fname string) error {
		fd, err := os.Open(fname)
		if err != nil {
			return err
		}
		stream1 := NewCmdStreamFile(fd)
		_, err = sh.Loop(stream1)
		fd.Close()
		if err == io.EOF {
			return nil
		} else {
			return err
		}
	}

	script, err := OptionParse(sh, &ScriptEngineForOptionImpl{})
	if err != nil {
		return err
	}

	if !isatty.IsTerminal(os.Stdin.Fd()) || script != nil {
		SilentMode = true
	}

	if !OptionNorc {
		if !SilentMode {
			fmt.Printf("Nihongo Yet Another GOing Shell %s-%s by %s\n",
				VersionOrStamp(),
				runtime.GOARCH,
				runtime.Version())
			fmt.Println("(c) 2014-2018 NYAOS.ORG <http://www.nyaos.org>")
		}
		if err := LoadScripts(shellEngine, langEngine); err != nil {
			fmt.Fprintln(os.Stderr, err.Error())
		}
	}

	if script != nil {
		if err := script(); err != nil {
			if err != io.EOF {
				return err
			} else {
				return nil
			}
		}
	}

	backupHistory := default_history
	defer func() {
		default_history = backupHistory
	}()

	var stream1 shell.Stream
	if isatty.IsTerminal(os.Stdin.Fd()) {
		constream := NewCmdStreamConsole(
			func() (int, error) {
				fmt.Fprint(readline.Console,
					Format2Prompt(os.Getenv("PROMPT")))
				return 0, nil
			})
		stream1 = constream
		default_history = constream.History
	} else {
		stream1 = NewCmdStreamFile(os.Stdin)
	}

	for {
		_, err := sh.Loop(&MainStream{stream1})
		if err == io.EOF {
			return err
		}
		if err != nil {
			fmt.Println(err.Error())
		}
	}
}
<|endoftext|>