<commit_before>// Package golang includes The runtime, which is just a stub that connects the cocoon code to the connector's RPC
// server. The runtime provides access to APIs for various operations.
package golang

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"time"

	"strings"

	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/common"
	"github.com/ncodes/cocoon/core/runtime/golang/config"
	"github.com/ncodes/cocoon/core/runtime/golang/proto"
	"github.com/ncodes/cocoon/core/types"
	"github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	"google.golang.org/grpc"
)

var (

	// serverAddr to bind to
	serverAddr = util.Env("COCOON_ADDR", ":8000")

	// stub logger
	log *logging.Logger

	// default running server
	defaultServer *stubServer

	// stop channel to stop the server/cocoon code
	serverDone chan bool

	// Default runtime link
	defaultLink = NewNativeLink(GetID())

	// txChannels holds the channels to send transaction responses to
	txRespChannels = cmap.New()

	// ErrAlreadyExist represents an error about an already existing resource
	ErrAlreadyExist = fmt.Errorf("already exists")

	// ErrNotConnected represents an error about the cocoon code not
	// having an active connection with the connector.
	ErrNotConnected = fmt.Errorf("not connected to the connector")

	// Flag to help tell whether cocoon code is running
	running = false

	// Number of transactions per block
	txPerBlock = util.Env("TX_PER_BLOCK", "100")

	// Time between block creation (seconds)
	blockCreationInterval = util.Env("BLOCK_CREATION_INT", "5")

	// blockMaker creates a collection of blockchain transactions at interval
	blockMaker *BlockMaker

	// The cocoon code currently running
	ccode CocoonCode
)

// GetLogger returns the stubs logger.
func GetLogger() *logging.Logger {
	return log
}

// SetDebugLevel sets the default logger debug level
func SetDebugLevel(level logging.Level) {
	logging.SetLevel(level, log.Module)
}

func init() {
	defaultServer = new(stubServer)
	config.ConfigureLogger()
	log = logging.MustGetLogger("ccode.runtime")
}

// GetID returns the cocoon id. However, it will return the
// natively linked cocoon id if this cocoon is linked to another
// cocoon.
func GetID() string {
	return util.Env("COCOON_LINK", os.Getenv("COCOON_ID"))
}

// GetCocoonID returns the unique cocoon id
func GetCocoonID() string {
	return os.Getenv("COCOON_ID")
}

// Run starts the stub server, takes a cocoon code and attempts to initialize it..
func Run(cc CocoonCode) {

	if running {
		log.Info("cocoon code is already running")
		return
	}

	go func() {
		for {
			log.Info("Alive!!")
			time.Sleep(10 * time.Second)
		}
	}()

	serverDone = make(chan bool, 1)

	lis, err := net.Listen("tcp", fmt.Sprintf("%s", serverAddr))
	if err != nil {
		log.Fatalf("failed to listen on port=%s", strings.Split(serverAddr, ":")[1])
	}

	log.Infof("Started stub service at port=%s", strings.Split(serverAddr, ":")[1])
	server := grpc.NewServer()
	proto.RegisterStubServer(server, defaultServer)

	// start server with the ability to restart
	// when it unexpectedly stops.
	go func() {
		maxRetries := 5
		retries := 1
		for {
			err := server.Serve(lis)
			log.Errorf("Server stopped: %s", err)
			retries++
			if retries == maxRetries {
				log.Errorf("RPC server stopped: %s", err)
				Stop(1)
				break
			}
			log.Info("Restarting server")
		}
	}()

	intTxPerBlock, _ := strconv.Atoi(txPerBlock)
	intBlkCreationInt, _ := strconv.Atoi(blockCreationInterval)
	blockMaker = NewBlockMaker(intTxPerBlock, time.Duration(intBlkCreationInt)*time.Second)
	go blockMaker.Begin(blockCommitter)

	defaultLink.SetDefaultLedger(types.GetGlobalLedgerName())

	ccode = cc

	// run Init() after 1 second to give time for connector to connect
	time.AfterFunc(1*time.Second, func() {
		if err = cc.OnInit(defaultLink); err != nil {
			log.Errorf("cocoode OnInit() returned error: %s", err)
			Stop(1)
		} else {
			running = true
		}
	})

	<-serverDone
	log.Info("Cocoon code stopped")
	os.Exit(0)
}

// blockCommit creates a PUT operation which adds one or many
// transactions to the store and blockchain and returns the block if
// if succeed or error if otherwise.
func blockCommitter(entries []*Entry) interface{} {

	var block types.Block

	if len(entries) == 0 {
		return block
	}

	txs := make([]*types.Transaction, len(entries))
	for i, e := range entries {
		txs[i] = e.Tx
	}

	ledgerName := entries[0].Tx.Ledger
	txsJSON, _ := util.ToJSON(txs)

	var respCh = make(chan *proto.Tx)

	txID := util.UUID4()
	err := sendTx(&proto.Tx{
		Id:     txID,
		Invoke: true,
		Name:   types.TxPut,
		LinkTo: entries[0].To,
		Params: []string{ledgerName},
		Body:   txsJSON,
	}, respCh)
	if err != nil {
		return fmt.Errorf("failed to put block transaction. %s", err)
	}

	resp, err := common.AwaitTxChan(respCh)
	if err != nil {
		return err
	}

	if resp.Status != 200 {
		return fmt.Errorf("%s", common.GetRPCErrDesc(fmt.Errorf("%s", resp.Body)))
	}

	if err = util.FromJSON(resp.Body, &block); err != nil {
		return fmt.Errorf("failed to unmarshall response data")
	}

	return &block
}

// sendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func sendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := defaultServer.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		log.Errorf("failed to send transaction [%s] to connector. %s", tx.GetId(), err)
		return err
	}
	log.Debugf("Successfully sent transaction [%s] to connector", tx.GetId())
	return nil
}

// Stop stub and cocoon code
func Stop(exitCode int) {
	running = false
	if blockMaker != nil {
		blockMaker.Stop()
	}
	defaultServer.stream = nil
	serverDone <- true
	log.Info("Cocoon code exiting with exit code %d", exitCode)
	os.Exit(exitCode)
}

// isConnected checks if connection with the connector
// is active.
func isConnected() bool {
	return defaultServer.stream != nil
}

// GetGlobalLedger returns the name of the global ledger.
func GetGlobalLedger() string {
	return types.GetGlobalLedgerName()
}
<commit_msg>Removed log<commit_after>// Package golang includes The runtime, which is just a stub that connects the cocoon code to the connector's RPC
// server. The runtime provides access to APIs for various operations.
package golang

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"time"

	"strings"

	"github.com/ellcrys/util"
	"github.com/ncodes/cocoon/core/common"
	"github.com/ncodes/cocoon/core/runtime/golang/config"
	"github.com/ncodes/cocoon/core/runtime/golang/proto"
	"github.com/ncodes/cocoon/core/types"
	"github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	"google.golang.org/grpc"
)

var (

	// serverAddr to bind to
	serverAddr = util.Env("COCOON_ADDR", ":8000")

	// stub logger
	log *logging.Logger

	// default running server
	defaultServer *stubServer

	// stop channel to stop the server/cocoon code
	serverDone chan bool

	// Default runtime link
	defaultLink = NewNativeLink(GetID())

	// txChannels holds the channels to send transaction responses to
	txRespChannels = cmap.New()

	// ErrAlreadyExist represents an error about an already existing resource
	ErrAlreadyExist = fmt.Errorf("already exists")

	// ErrNotConnected represents an error about the cocoon code not
	// having an active connection with the connector.
	ErrNotConnected = fmt.Errorf("not connected to the connector")

	// Flag to help tell whether cocoon code is running
	running = false

	// Number of transactions per block
	txPerBlock = util.Env("TX_PER_BLOCK", "100")

	// Time between block creation (seconds)
	blockCreationInterval = util.Env("BLOCK_CREATION_INT", "5")

	// blockMaker creates a collection of blockchain transactions at interval
	blockMaker *BlockMaker

	// The cocoon code currently running
	ccode CocoonCode
)

// GetLogger returns the stubs logger.
func GetLogger() *logging.Logger {
	return log
}

// SetDebugLevel sets the default logger debug level
func SetDebugLevel(level logging.Level) {
	logging.SetLevel(level, log.Module)
}

func init() {
	defaultServer = new(stubServer)
	config.ConfigureLogger()
	log = logging.MustGetLogger("ccode.runtime")
}

// GetID returns the cocoon id. However, it will return the
// natively linked cocoon id if this cocoon is linked to another
// cocoon.
func GetID() string {
	return util.Env("COCOON_LINK", os.Getenv("COCOON_ID"))
}

// GetCocoonID returns the unique cocoon id
func GetCocoonID() string {
	return os.Getenv("COCOON_ID")
}

// Run starts the stub server, takes a cocoon code and attempts to initialize it..
func Run(cc CocoonCode) {

	if running {
		log.Info("cocoon code is already running")
		return
	}

	serverDone = make(chan bool, 1)

	lis, err := net.Listen("tcp", fmt.Sprintf("%s", serverAddr))
	if err != nil {
		log.Fatalf("failed to listen on port=%s", strings.Split(serverAddr, ":")[1])
	}

	log.Infof("Started stub service at port=%s", strings.Split(serverAddr, ":")[1])
	server := grpc.NewServer()
	proto.RegisterStubServer(server, defaultServer)

	// start server with the ability to restart
	// when it unexpectedly stops.
	go func() {
		maxRetries := 5
		retries := 1
		for {
			err := server.Serve(lis)
			log.Errorf("Server stopped: %s", err)
			retries++
			if retries == maxRetries {
				log.Errorf("RPC server stopped: %s", err)
				Stop(1)
				break
			}
			log.Info("Restarting server")
		}
	}()

	intTxPerBlock, _ := strconv.Atoi(txPerBlock)
	intBlkCreationInt, _ := strconv.Atoi(blockCreationInterval)
	blockMaker = NewBlockMaker(intTxPerBlock, time.Duration(intBlkCreationInt)*time.Second)
	go blockMaker.Begin(blockCommitter)

	defaultLink.SetDefaultLedger(types.GetGlobalLedgerName())

	ccode = cc

	// run Init() after 1 second to give time for connector to connect
	time.AfterFunc(1*time.Second, func() {
		if err = cc.OnInit(defaultLink); err != nil {
			log.Errorf("cocoode OnInit() returned error: %s", err)
			Stop(1)
		} else {
			running = true
		}
	})

	<-serverDone
	log.Info("Cocoon code stopped")
	os.Exit(0)
}

// blockCommit creates a PUT operation which adds one or many
// transactions to the store and blockchain and returns the block if
// if succeed or error if otherwise.
func blockCommitter(entries []*Entry) interface{} {

	var block types.Block

	if len(entries) == 0 {
		return block
	}

	txs := make([]*types.Transaction, len(entries))
	for i, e := range entries {
		txs[i] = e.Tx
	}

	ledgerName := entries[0].Tx.Ledger
	txsJSON, _ := util.ToJSON(txs)

	var respCh = make(chan *proto.Tx)

	txID := util.UUID4()
	err := sendTx(&proto.Tx{
		Id:     txID,
		Invoke: true,
		Name:   types.TxPut,
		LinkTo: entries[0].To,
		Params: []string{ledgerName},
		Body:   txsJSON,
	}, respCh)
	if err != nil {
		return fmt.Errorf("failed to put block transaction. %s", err)
	}

	resp, err := common.AwaitTxChan(respCh)
	if err != nil {
		return err
	}

	if resp.Status != 200 {
		return fmt.Errorf("%s", common.GetRPCErrDesc(fmt.Errorf("%s", resp.Body)))
	}

	if err = util.FromJSON(resp.Body, &block); err != nil {
		return fmt.Errorf("failed to unmarshall response data")
	}

	return &block
}

// sendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func sendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := defaultServer.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		log.Errorf("failed to send transaction [%s] to connector. %s", tx.GetId(), err)
		return err
	}
	log.Debugf("Successfully sent transaction [%s] to connector", tx.GetId())
	return nil
}

// Stop stub and cocoon code
func Stop(exitCode int) {
	running = false
	if blockMaker != nil {
		blockMaker.Stop()
	}
	defaultServer.stream = nil
	serverDone <- true
	log.Info("Cocoon code exiting with exit code %d", exitCode)
	os.Exit(exitCode)
}

// isConnected checks if connection with the connector
// is active.
func isConnected() bool {
	return defaultServer.stream != nil
}

// GetGlobalLedger returns the name of the global ledger.
func GetGlobalLedger() string {
	return types.GetGlobalLedgerName()
}
<|endoftext|>


<commit_before>package ninja

import (
	"fmt"

	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"github.com/bitly/go-simplejson"
	"github.com/ninjasphere/go-ninja/logger"
)

type JsonMessageHandler func(string, *simplejson.Json)

// ChannelBus context for channel related bus operations.
type ChannelBus struct {
	name     string
	protocol string
	device   *DeviceBus
	channel  <-chan MQTT.Receipt
	log      *logger.Logger
}

// NewChannelBus Build a new channel bus for the supplied device
func NewChannelBus(name string, protocol string, d *DeviceBus) *ChannelBus {
	log := logger.GetLogger(fmt.Sprintf("channel.%s.%s", name, protocol))
	log.Infof("Created")
	return &ChannelBus{
		name:     name,
		protocol: protocol,
		device:   d,
		log:      log,
	}
}

// SendEvent Publish an event on the channel bus.
func (cb *ChannelBus) SendEvent(event string, payload *simplejson.Json) error {
	cb.log.Debugf("sending event %s", event)
	json, err := payload.MarshalJSON()
	if err != nil {
		return err
	}

	receipt := cb.device.driver.mqtt.Publish(MQTT.QoS(0), "$driver/"+cb.device.driver.id+"/device/"+cb.device.id+"/channel/"+cb.name+"/"+cb.protocol+"/event/"+event, json)
	<-receipt

	return nil
}
<commit_msg>Remove more unnecessary logging<commit_after>package ninja

import (
	"fmt"

	MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git"
	"github.com/bitly/go-simplejson"
	"github.com/ninjasphere/go-ninja/logger"
)

type JsonMessageHandler func(string, *simplejson.Json)

// ChannelBus context for channel related bus operations.
type ChannelBus struct {
	name     string
	protocol string
	device   *DeviceBus
	channel  <-chan MQTT.Receipt
	log      *logger.Logger
}

// NewChannelBus Build a new channel bus for the supplied device
func NewChannelBus(name string, protocol string, d *DeviceBus) *ChannelBus {
	log := logger.GetLogger(fmt.Sprintf("channel.%s.%s", name, protocol))
	return &ChannelBus{
		name:     name,
		protocol: protocol,
		device:   d,
		log:      log,
	}
}

// SendEvent Publish an event on the channel bus.
func (cb *ChannelBus) SendEvent(event string, payload *simplejson.Json) error {
	cb.log.Debugf("sending event %s", event)
	json, err := payload.MarshalJSON()
	if err != nil {
		return err
	}

	receipt := cb.device.driver.mqtt.Publish(MQTT.QoS(0), "$driver/"+cb.device.driver.id+"/device/"+cb.device.id+"/channel/"+cb.name+"/"+cb.protocol+"/event/"+event, json)
	<-receipt

	return nil
}
<|endoftext|>


<commit_before>package loader

import (
	"context"
	"fmt"
	"os"
	"testing"

	"github.com/lukaszbudnik/migrator/config"
	"github.com/stretchr/testify/assert"
)

func TestAzureGetSourceMigrations(t *testing.T) {

	accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY")

	if len(accountName) == 0 || len(accountKey) == 0 {
		t.Skip("skipping test AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY not set")
	}

	// migrator implements env variable substitution and normally we would use:
	// "https://${AZURE_STORAGE_ACCOUNT}.blob.core.windows.net/mycontainer"
	// however below we are creating the Config struct directly
	// and that's why we need to build correct URL ourselves
	baseLocation := fmt.Sprintf("https://%v.blob.core.windows.net/mycontainer", accountName)

	config := &config.Config{
		BaseLocation:     baseLocation,
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	migrations := loader.GetSourceMigrations()

	assert.Len(t, migrations, 12)

	assert.Contains(t, migrations[0].File, "migrations/config/201602160001.sql")
	assert.Contains(t, migrations[1].File, "migrations/config/201602160002.sql")
	assert.Contains(t, migrations[2].File, "migrations/tenants/201602160002.sql")
	assert.Contains(t, migrations[3].File, "migrations/ref/201602160003.sql")
	assert.Contains(t, migrations[4].File, "migrations/tenants/201602160003.sql")
	assert.Contains(t, migrations[5].File, "migrations/ref/201602160004.sql")
	assert.Contains(t, migrations[6].File, "migrations/tenants/201602160004.sql")
	assert.Contains(t, migrations[7].File, "migrations/tenants/201602160005.sql")
	assert.Contains(t, migrations[8].File, "migrations/config-scripts/200012181227.sql")
	assert.Contains(t, migrations[9].File, "migrations/tenants-scripts/200001181228.sql")
	assert.Contains(t, migrations[10].File, "migrations/tenants-scripts/a.sql")
	assert.Contains(t, migrations[11].File, "migrations/tenants-scripts/b.sql")

}

func TestAzureGetSourceMigrationsWithOptionalPrefix(t *testing.T) {

	accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY")

	if len(accountName) == 0 || len(accountKey) == 0 {
		t.Skip("skipping test AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY not set")
	}

	baseLocation := fmt.Sprintf("https://%v.blob.core.windows.net/myothercontainer/prod/artefacts/", accountName)

	config := &config.Config{
		BaseLocation:     baseLocation,
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	migrations := loader.GetSourceMigrations()

	assert.Len(t, migrations, 12)

	assert.Contains(t, migrations[0].File, "prod/artefacts/migrations/config/201602160001.sql")
	assert.Contains(t, migrations[1].File, "prod/artefacts/migrations/config/201602160002.sql")
	assert.Contains(t, migrations[2].File, "prod/artefacts/migrations/tenants/201602160002.sql")
	assert.Contains(t, migrations[3].File, "prod/artefacts/migrations/ref/201602160003.sql")
	assert.Contains(t, migrations[4].File, "prod/artefacts/migrations/tenants/201602160003.sql")
	assert.Contains(t, migrations[5].File, "prod/artefacts/migrations/ref/201602160004.sql")
	assert.Contains(t, migrations[6].File, "prod/artefacts/migrations/tenants/201602160004.sql")
	assert.Contains(t, migrations[7].File, "prod/artefacts/migrations/tenants/201602160005.sql")
	assert.Contains(t, migrations[8].File, "prod/artefacts/migrations/config-scripts/200012181227.sql")
	assert.Contains(t, migrations[9].File, "prod/artefacts/migrations/tenants-scripts/200001181228.sql")
	assert.Contains(t, migrations[10].File, "prod/artefacts/migrations/tenants-scripts/a.sql")
	assert.Contains(t, migrations[11].File, "prod/artefacts/migrations/tenants-scripts/b.sql")

}

func TestAzureHealthCheck(t *testing.T) {
	accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY")

	if len(accountName) == 0 || len(accountKey) == 0 {
		t.Skip("skipping test AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY not set")
	}

	baseLocation := fmt.Sprintf("https://%v.blob.core.windows.net/myothercontainer/prod/artefacts/", accountName)

	config := &config.Config{
		BaseLocation:     baseLocation,
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	err := loader.HealthCheck()
	assert.Nil(t, err)
}
<commit_msg>restored test but with no assertions<commit_after>package loader

import (
	"context"
	"fmt"
	"os"
	"testing"

	"github.com/lukaszbudnik/migrator/config"
	"github.com/stretchr/testify/assert"
)

func TestAzureGetSourceMigrations(t *testing.T) {

	accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY")

	if len(accountName) == 0 || len(accountKey) == 0 {
		t.Skip("skipping test AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY not set")
	}

	// migrator implements env variable substitution and normally we would use:
	// "https://${AZURE_STORAGE_ACCOUNT}.blob.core.windows.net/mycontainer"
	// however below we are creating the Config struct directly
	// and that's why we need to build correct URL ourselves
	baseLocation := fmt.Sprintf("https://%v.blob.core.windows.net/mycontainer", accountName)

	config := &config.Config{
		BaseLocation:     baseLocation,
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	migrations := loader.GetSourceMigrations()

	assert.Len(t, migrations, 12)

	assert.Contains(t, migrations[0].File, "migrations/config/201602160001.sql")
	assert.Contains(t, migrations[1].File, "migrations/config/201602160002.sql")
	assert.Contains(t, migrations[2].File, "migrations/tenants/201602160002.sql")
	assert.Contains(t, migrations[3].File, "migrations/ref/201602160003.sql")
	assert.Contains(t, migrations[4].File, "migrations/tenants/201602160003.sql")
	assert.Contains(t, migrations[5].File, "migrations/ref/201602160004.sql")
	assert.Contains(t, migrations[6].File, "migrations/tenants/201602160004.sql")
	assert.Contains(t, migrations[7].File, "migrations/tenants/201602160005.sql")
	assert.Contains(t, migrations[8].File, "migrations/config-scripts/200012181227.sql")
	assert.Contains(t, migrations[9].File, "migrations/tenants-scripts/200001181228.sql")
	assert.Contains(t, migrations[10].File, "migrations/tenants-scripts/a.sql")
	assert.Contains(t, migrations[11].File, "migrations/tenants-scripts/b.sql")

}

func TestAzureGetSourceMigrationsWithOptionalPrefix(t *testing.T) {

	accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY")

	if len(accountName) == 0 || len(accountKey) == 0 {
		t.Skip("skipping test AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY not set")
	}

	baseLocation := fmt.Sprintf("https://%v.blob.core.windows.net/myothercontainer/prod/artefacts/", accountName)

	config := &config.Config{
		BaseLocation:     baseLocation,
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	migrations := loader.GetSourceMigrations()

	assert.Len(t, migrations, 12)

	assert.Contains(t, migrations[0].File, "prod/artefacts/migrations/config/201602160001.sql")
	assert.Contains(t, migrations[1].File, "prod/artefacts/migrations/config/201602160002.sql")
	assert.Contains(t, migrations[2].File, "prod/artefacts/migrations/tenants/201602160002.sql")
	assert.Contains(t, migrations[3].File, "prod/artefacts/migrations/ref/201602160003.sql")
	assert.Contains(t, migrations[4].File, "prod/artefacts/migrations/tenants/201602160003.sql")
	assert.Contains(t, migrations[5].File, "prod/artefacts/migrations/ref/201602160004.sql")
	assert.Contains(t, migrations[6].File, "prod/artefacts/migrations/tenants/201602160004.sql")
	assert.Contains(t, migrations[7].File, "prod/artefacts/migrations/tenants/201602160005.sql")
	assert.Contains(t, migrations[8].File, "prod/artefacts/migrations/config-scripts/200012181227.sql")
	assert.Contains(t, migrations[9].File, "prod/artefacts/migrations/tenants-scripts/200001181228.sql")
	assert.Contains(t, migrations[10].File, "prod/artefacts/migrations/tenants-scripts/a.sql")
	assert.Contains(t, migrations[11].File, "prod/artefacts/migrations/tenants-scripts/b.sql")

}

func TestAzureHealthCheck(t *testing.T) {
	accountName, accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT"), os.Getenv("AZURE_STORAGE_ACCESS_KEY")

	if len(accountName) == 0 || len(accountKey) == 0 {
		t.Skip("skipping test AZURE_STORAGE_ACCOUNT or AZURE_STORAGE_ACCESS_KEY not set")
	}

	baseLocation := fmt.Sprintf("https://%v.blob.core.windows.net/myothercontainer/prod/artefacts/", accountName)

	config := &config.Config{
		BaseLocation:     baseLocation,
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	err := loader.HealthCheck()
	assert.Nil(t, err)
}

func TestAzureMsiCredentials(t *testing.T) {
	// in CI/CD env the MSI credentials are not available
	// this code just assures that if no shared key envs are present it will fallback to MSI
	// unsetting one of the shared key envs will cause fallback to MSI
	os.Unsetenv("AZURE_STORAGE_ACCESS_KEY")

	config := &config.Config{
		BaseLocation:     "https://justtesting.blob.core.windows.net/myothercontainer/prod/artefacts/",
		SingleMigrations: []string{"migrations/config", "migrations/ref"},
		TenantMigrations: []string{"migrations/tenants"},
		SingleScripts:    []string{"migrations/config-scripts"},
		TenantScripts:    []string{"migrations/tenants-scripts"},
	}

	loader := &azureBlobLoader{baseLoader{context.TODO(), config}}
	loader.getAzureStorageCredentials()
}
<|endoftext|>


<commit_before>package artnet

import (
	"bytes"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/jsimonetti/go-artnet/packet"
	"github.com/jsimonetti/go-artnet/packet/code"
)

var broadcastAddr = net.UDPAddr{
	IP:   []byte{0x02, 0xff, 0xff, 0xff},
	Port: int(packet.ArtNetPort),
}

// we poll for new nodes every 3 seconds
var pollInterval = 3 * time.Second

// ControlledNode hols the configuration of a node we control
type ControlledNode struct {
	LastSeen   time.Time
	Node       NodeConfig
	UDPAddress net.UDPAddr

	Sequence  uint8
	DMXBuffer map[Address]*dmxBuffer
	nodeLock  sync.Mutex
}

type dmxBuffer struct {
	Data       [512]byte
	LastUpdate time.Time
	Stale      bool
}

// setDMXBuffer will update the buffer on a universe address
func (cn *ControlledNode) setDMXBuffer(dmx [512]byte, address Address) error {
	cn.nodeLock.Lock()
	defer cn.nodeLock.Unlock()

	var buf *dmxBuffer
	var ok bool

	if buf, ok = cn.DMXBuffer[address]; !ok {
		return fmt.Errorf("unknown address for controlled node")
	}

	buf.Data = dmx
	buf.Stale = true

	return nil
}

// dmxUpdate will create an ArtDMXPacket and marshal it into bytes
func (cn *ControlledNode) dmxUpdate(address Address) (b []byte, err error) {
	var buf *dmxBuffer
	var ok bool

	cn.nodeLock.Lock()
	defer cn.nodeLock.Unlock()

	if buf, ok = cn.DMXBuffer[address]; !ok {
		return nil, fmt.Errorf("unknown address for controlled node")
	}

	cn.Sequence++
	p := &packet.ArtDMXPacket{
		Sequence: cn.Sequence,
		SubUni:   address.SubUni,
		Net:      address.Net,
		Length:   uint16(len(cn.DMXBuffer)),
		Data:     buf.Data,
	}
	b, err = p.MarshalBinary()
	return
}

// Controller holds the information for a controller
type Controller struct {
	// cNode is the Node for the cNode
	cNode *Node

	// Nodes is a slice of nodes that are seen by this controller
	Nodes         []*ControlledNode
	OutputAddress map[Address]*ControlledNode
	InputAddress  map[Address]*ControlledNode
	nodeLock      sync.Mutex

	shutdownCh chan struct{}

	maxFPS int
	log    Logger

	pollTicker *time.Ticker
	gcTicker   *time.Ticker
}

// NewController return a Controller
func NewController(name string, ip net.IP, log Logger, opts ...Option) *Controller {
	c := &Controller{
		cNode:  NewNode(name, code.StController, ip, log),
		log:    log,
		maxFPS: 1000,
	}

	for _, opt := range opts {
		c.SetOption(opt)
	}

	return c
}

// Start will start this controller
func (c *Controller) Start() error {
	c.OutputAddress = make(map[Address]*ControlledNode)
	c.InputAddress = make(map[Address]*ControlledNode)
	c.shutdownCh = make(chan struct{})
	c.cNode.log = c.log.With(Fields{"type": "Node"})
	c.log = c.log.With(Fields{"type": "Controller"})
	if err := c.cNode.Start(); err != nil {
		return fmt.Errorf("failed to start controller node: %v", err)
	}

	c.pollTicker = time.NewTicker(pollInterval)
	c.gcTicker = time.NewTicker(pollInterval)

	go c.pollLoop()
	go c.dmxUpdateLoop()
	return c.cNode.shutdownErr
}

// Stop will stop this controller
func (c *Controller) Stop() {
	c.pollTicker.Stop()
	c.gcTicker.Stop()
	c.cNode.Stop()

	select {
	case <-c.cNode.shutdownCh:
	}

	close(c.shutdownCh)
}

// pollLoop will routinely poll for new nodes
func (c *Controller) pollLoop() {
	artPoll := &packet.ArtPollPacket{
		TalkToMe: new(code.TalkToMe).WithReplyOnChange(true),
		Priority: code.DpAll,
	}

	// create an ArtPoll packet to send out periodically
	b, err := artPoll.MarshalBinary()
	if err != nil {
		c.log.With(Fields{"err": err}).Error("error creating ArtPoll packet")
		return
	}

	// send ArtPollPacket
	c.cNode.sendCh <- netPayload{
		address: broadcastAddr,
		data:    b,
	}
	c.cNode.pollCh <- packet.ArtPollPacket{}

	// loop until shutdown
	for {
		select {
		case <-c.pollTicker.C:
			// send ArtPollPacket
			c.cNode.sendCh <- netPayload{
				address: broadcastAddr,
				data:    b,
			}
			c.cNode.pollCh <- packet.ArtPollPacket{}

		case <-c.gcTicker.C:
			// clean up old nodes
			c.gcNode()

		case p := <-c.cNode.pollReplyCh:
			cfg := ConfigFromArtPollReply(p)
			if cfg.Type != code.StNode {
				// we don't care for ArtNet devices other then nodes for now @todo
				continue
			}

			if err := c.updateNode(cfg); err != nil {
				c.log.With(Fields{"err": err}).Error("error updating node")
			}

		case <-c.shutdownCh:
			return
		}
	}
}

// SendDMXToAddress will set the DMXBuffer for a destination address
// and update the node
func (c *Controller) SendDMXToAddress(dmx [512]byte, address Address) {
	c.log.With(Fields{"address": address.String()}).Debug("received update channels")

	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	var cn *ControlledNode
	var ok bool

	if cn, ok = c.OutputAddress[address]; !ok {
		c.log.With(Fields{"address": address.String()}).Error("could not find node for address")
		return
	}
	err := cn.setDMXBuffer(dmx, address)
	if err != nil {
		c.log.With(Fields{"err": err, "address": address.String()}).Error("error setting buffer on address")
		return
	}
}

// dmxUpdateLoop will periodically update nodes until shutdown
func (c *Controller) dmxUpdateLoop() {
	fpsInterval := time.Duration(c.maxFPS)
	ticker := time.NewTicker(time.Second / fpsInterval)

	forceUpdate := 250 * time.Millisecond

	update := func(node *ControlledNode, address Address, now time.Time) error {
		// get an ArtDMXPacket for this node
		b, err := node.dmxUpdate(address)
		if err != nil {
			return err
		}
		node.DMXBuffer[address].LastUpdate = now
		node.DMXBuffer[address].Stale = false

		c.cNode.sendCh <- netPayload{
			address: node.UDPAddress,
			data:    b,
		}
		return nil
	}

	// loop until shutdown
	for {
		select {
		case <-ticker.C:
			now := time.Now()
			// send DMX buffer update
			c.nodeLock.Lock()
			for address, node := range c.OutputAddress {
				// only update if it has been X seconds
				if node.DMXBuffer[address].Stale && node.DMXBuffer[address].LastUpdate.Before(now.Add(-fpsInterval)) {
					err := update(node, address, now)
					if err != nil {
						c.log.With(Fields{"err": err, "address": address.String()}).Error("error getting buffer for address")
						continue
					}
				}
				if node.DMXBuffer[address].LastUpdate.Before(now.Add(-forceUpdate)) {
					err := update(node, address, now)
					if err != nil {
						c.log.With(Fields{"err": err, "address": address.String()}).Error("error getting buffer for address")
						continue
					}
				}
			}
			c.nodeLock.Unlock()

		case <-c.shutdownCh:
			return
		}
	}
}

// updateNode will add a Node to the list of known nodes
// this assumes that there are no universe address collisions
// in the future we should probably be prepared to handle that too
func (c *Controller) updateNode(cfg NodeConfig) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i := range c.Nodes {
		if bytes.Equal(cfg.IP, c.Nodes[i].Node.IP) {
			// update this node, since we allready know about it
			c.log.With(Fields{"node": cfg.Name, "ip": cfg.IP.String()}).Debug("updated node")
			// remove references to this node from the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				delete(c.OutputAddress, port.Address)
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				delete(c.InputAddress, port.Address)
			}
			c.Nodes[i].Node = cfg
			c.Nodes[i].LastSeen = time.Now()
			// add references to this node to the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				c.OutputAddress[port.Address] = c.Nodes[i]
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				c.InputAddress[port.Address] = c.Nodes[i]
			}
			return nil
		}
	}

	// create an empty DMX buffer. This will blackout the node entirely
	buf := make(map[Address]*dmxBuffer)
	for _, port := range cfg.OutputPorts {
		buf[port.Address] = &dmxBuffer{}
	}

	// new node, add it to our known nodes
	c.log.With(Fields{"node": cfg.Name, "ip": cfg.IP.String()}).Debug("added node")
	node := &ControlledNode{
		Node:       cfg,
		DMXBuffer:  buf,
		LastSeen:   time.Now(),
		Sequence:   0,
		UDPAddress: net.UDPAddr{IP: cfg.IP, Port: int(packet.ArtNetPort)},
	}
	c.Nodes = append(c.Nodes, node)

	// add references to this node to the output map
	for _, port := range node.Node.OutputPorts {
		c.OutputAddress[port.Address] = node
	}
	for _, port := range node.Node.InputPorts {
		c.InputAddress[port.Address] = node
	}

	return nil
}

// deleteNode will delete a Node from the list of known nodes
func (c *Controller) deleteNode(node NodeConfig) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i := range c.Nodes {
		if bytes.Equal(node.IP, c.Nodes[i].Node.IP) {
			// node found, remove it from the list
			// remove references to this node from the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				delete(c.OutputAddress, port.Address)
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				delete(c.InputAddress, port.Address)
			}
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
		}
	}

	return fmt.Errorf("no known node with this ip known, ip: %s", node.IP)
}

// gcNode will remove stale Nodes from the list of known nodes
// it will loop through the list of nodes and remove nodes older then X seconds
func (c *Controller) gcNode() {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	// nodes are stale after 5 missed ArtPoll's
	//staleAfter, _ := time.ParseDuration(fmt.Sprintf("%ds", 5*pollInterval))
	staleAfter := 7 * time.Second

start:
	for i := range c.Nodes {
		if c.Nodes[i].LastSeen.Add(staleAfter).Before(time.Now()) {
			// it has been more then X seconds since we saw this node. remove it now.
			c.log.With(Fields{"node": c.Nodes[i].Node.Name, "ip": c.Nodes[i].Node.IP.String()}).Debug("remove stale node")

			// remove references to this node from the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				delete(c.OutputAddress, port.Address)
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				delete(c.InputAddress, port.Address)
			}
			// remove node
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
			goto start
		}
	}
}
<commit_msg>Add handling for controllers with output and treat them as nodes for now (#22)<commit_after>package artnet

import (
	"bytes"
	"fmt"
	"net"
	"sync"
	"time"

	"github.com/jsimonetti/go-artnet/packet"
	"github.com/jsimonetti/go-artnet/packet/code"
)

var broadcastAddr = net.UDPAddr{
	IP:   []byte{0x02, 0xff, 0xff, 0xff},
	Port: int(packet.ArtNetPort),
}

// we poll for new nodes every 3 seconds
var pollInterval = 3 * time.Second

// ControlledNode hols the configuration of a node we control
type ControlledNode struct {
	LastSeen   time.Time
	Node       NodeConfig
	UDPAddress net.UDPAddr

	Sequence  uint8
	DMXBuffer map[Address]*dmxBuffer
	nodeLock  sync.Mutex
}

type dmxBuffer struct {
	Data       [512]byte
	LastUpdate time.Time
	Stale      bool
}

// setDMXBuffer will update the buffer on a universe address
func (cn *ControlledNode) setDMXBuffer(dmx [512]byte, address Address) error {
	cn.nodeLock.Lock()
	defer cn.nodeLock.Unlock()

	var buf *dmxBuffer
	var ok bool

	if buf, ok = cn.DMXBuffer[address]; !ok {
		return fmt.Errorf("unknown address for controlled node")
	}

	buf.Data = dmx
	buf.Stale = true

	return nil
}

// dmxUpdate will create an ArtDMXPacket and marshal it into bytes
func (cn *ControlledNode) dmxUpdate(address Address) (b []byte, err error) {
	var buf *dmxBuffer
	var ok bool

	cn.nodeLock.Lock()
	defer cn.nodeLock.Unlock()

	if buf, ok = cn.DMXBuffer[address]; !ok {
		return nil, fmt.Errorf("unknown address for controlled node")
	}

	cn.Sequence++
	p := &packet.ArtDMXPacket{
		Sequence: cn.Sequence,
		SubUni:   address.SubUni,
		Net:      address.Net,
		Length:   uint16(len(cn.DMXBuffer)),
		Data:     buf.Data,
	}
	b, err = p.MarshalBinary()
	return
}

// Controller holds the information for a controller
type Controller struct {
	// cNode is the Node for the cNode
	cNode *Node

	// Nodes is a slice of nodes that are seen by this controller
	Nodes         []*ControlledNode
	OutputAddress map[Address]*ControlledNode
	InputAddress  map[Address]*ControlledNode
	nodeLock      sync.Mutex

	shutdownCh chan struct{}

	maxFPS int
	log    Logger

	pollTicker *time.Ticker
	gcTicker   *time.Ticker
}

// NewController return a Controller
func NewController(name string, ip net.IP, log Logger, opts ...Option) *Controller {
	c := &Controller{
		cNode:  NewNode(name, code.StController, ip, log),
		log:    log,
		maxFPS: 1000,
	}

	for _, opt := range opts {
		c.SetOption(opt)
	}

	return c
}

// Start will start this controller
func (c *Controller) Start() error {
	c.OutputAddress = make(map[Address]*ControlledNode)
	c.InputAddress = make(map[Address]*ControlledNode)
	c.shutdownCh = make(chan struct{})
	c.cNode.log = c.log.With(Fields{"type": "Node"})
	c.log = c.log.With(Fields{"type": "Controller"})
	if err := c.cNode.Start(); err != nil {
		return fmt.Errorf("failed to start controller node: %v", err)
	}

	c.pollTicker = time.NewTicker(pollInterval)
	c.gcTicker = time.NewTicker(pollInterval)

	go c.pollLoop()
	go c.dmxUpdateLoop()
	return c.cNode.shutdownErr
}

// Stop will stop this controller
func (c *Controller) Stop() {
	c.pollTicker.Stop()
	c.gcTicker.Stop()
	c.cNode.Stop()

	select {
	case <-c.cNode.shutdownCh:
	}

	close(c.shutdownCh)
}

// pollLoop will routinely poll for new nodes
func (c *Controller) pollLoop() {
	artPoll := &packet.ArtPollPacket{
		TalkToMe: new(code.TalkToMe).WithReplyOnChange(true),
		Priority: code.DpAll,
	}

	// create an ArtPoll packet to send out periodically
	b, err := artPoll.MarshalBinary()
	if err != nil {
		c.log.With(Fields{"err": err}).Error("error creating ArtPoll packet")
		return
	}

	// send ArtPollPacket
	c.cNode.sendCh <- netPayload{
		address: broadcastAddr,
		data:    b,
	}
	c.cNode.pollCh <- packet.ArtPollPacket{}

	// loop until shutdown
	for {
		select {
		case <-c.pollTicker.C:
			// send ArtPollPacket
			c.cNode.sendCh <- netPayload{
				address: broadcastAddr,
				data:    b,
			}
			c.cNode.pollCh <- packet.ArtPollPacket{}

		case <-c.gcTicker.C:
			// clean up old nodes
			c.gcNode()

		case p := <-c.cNode.pollReplyCh:
			cfg := ConfigFromArtPollReply(p)

			if cfg.Type != code.StNode && cfg.Type != code.StController {
				// we don't care for ArtNet devices other then nodes and controllers for now @todo
				continue
			}

			if cfg.Type == code.StController && len(cfg.OutputPorts) == 0 {
				// we don't care for controllers which do not have output ports for now // @todo
				// otherwise we simply treat controllers like nodes unless controller to controller
				// communication is implemented according to Art-Net specification
				continue
			}

			if err := c.updateNode(cfg); err != nil {
				c.log.With(Fields{"err": err}).Error("error updating node")
			}

		case <-c.shutdownCh:
			return
		}
	}
}

// SendDMXToAddress will set the DMXBuffer for a destination address
// and update the node
func (c *Controller) SendDMXToAddress(dmx [512]byte, address Address) {
	c.log.With(Fields{"address": address.String()}).Debug("received update channels")

	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	var cn *ControlledNode
	var ok bool

	if cn, ok = c.OutputAddress[address]; !ok {
		c.log.With(Fields{"address": address.String()}).Error("could not find node for address")
		return
	}
	err := cn.setDMXBuffer(dmx, address)
	if err != nil {
		c.log.With(Fields{"err": err, "address": address.String()}).Error("error setting buffer on address")
		return
	}
}

// dmxUpdateLoop will periodically update nodes until shutdown
func (c *Controller) dmxUpdateLoop() {
	fpsInterval := time.Duration(c.maxFPS)
	ticker := time.NewTicker(time.Second / fpsInterval)

	forceUpdate := 250 * time.Millisecond

	update := func(node *ControlledNode, address Address, now time.Time) error {
		// get an ArtDMXPacket for this node
		b, err := node.dmxUpdate(address)
		if err != nil {
			return err
		}
		node.DMXBuffer[address].LastUpdate = now
		node.DMXBuffer[address].Stale = false

		c.cNode.sendCh <- netPayload{
			address: node.UDPAddress,
			data:    b,
		}
		return nil
	}

	// loop until shutdown
	for {
		select {
		case <-ticker.C:
			now := time.Now()
			// send DMX buffer update
			c.nodeLock.Lock()
			for address, node := range c.OutputAddress {
				if node.DMXBuffer[address] == nil {
					node.DMXBuffer[address] = &dmxBuffer{}
				}
				// only update if it has been X seconds
				if node.DMXBuffer[address].Stale && node.DMXBuffer[address].LastUpdate.Before(now.Add(-fpsInterval)) {
					err := update(node, address, now)
					if err != nil {
						c.log.With(Fields{"err": err, "address": address.String()}).Error("error getting buffer for address")
						continue
					}
				}
				if node.DMXBuffer[address].LastUpdate.Before(now.Add(-forceUpdate)) {
					err := update(node, address, now)
					if err != nil {
						c.log.With(Fields{"err": err, "address": address.String()}).Error("error getting buffer for address")
						continue
					}
				}
			}
			c.nodeLock.Unlock()

		case <-c.shutdownCh:
			return
		}
	}
}

// updateNode will add a Node to the list of known nodes
// this assumes that there are no universe address collisions
// in the future we should probably be prepared to handle that too
func (c *Controller) updateNode(cfg NodeConfig) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i := range c.Nodes {
		if bytes.Equal(cfg.IP, c.Nodes[i].Node.IP) {
			// update this node, since we already know about it
			c.log.With(Fields{"node": cfg.Name, "ip": cfg.IP.String()}).Debug("updated node")
			// remove references to this node from the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				delete(c.OutputAddress, port.Address)
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				delete(c.InputAddress, port.Address)
			}
			c.Nodes[i].Node = cfg
			c.Nodes[i].LastSeen = time.Now()
			// add references to this node to the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				c.OutputAddress[port.Address] = c.Nodes[i]
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				c.InputAddress[port.Address] = c.Nodes[i]
			}
			return nil
		}
	}

	// create an empty DMX buffer. This will blackout the node entirely
	buf := make(map[Address]*dmxBuffer)
	for _, port := range cfg.OutputPorts {
		buf[port.Address] = &dmxBuffer{}
	}

	// new node, add it to our known nodes
	c.log.With(Fields{"node": cfg.Name, "ip": cfg.IP.String()}).Debug("added node")
	node := &ControlledNode{
		Node:       cfg,
		DMXBuffer:  buf,
		LastSeen:   time.Now(),
		Sequence:   0,
		UDPAddress: net.UDPAddr{IP: cfg.IP, Port: packet.ArtNetPort},
	}
	c.Nodes = append(c.Nodes, node)

	// add references to this node to the output map
	for _, port := range node.Node.OutputPorts {
		c.OutputAddress[port.Address] = node
	}
	for _, port := range node.Node.InputPorts {
		c.InputAddress[port.Address] = node
	}

	return nil
}

// deleteNode will delete a Node from the list of known nodes
func (c *Controller) deleteNode(node NodeConfig) error {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	for i := range c.Nodes {
		if bytes.Equal(node.IP, c.Nodes[i].Node.IP) {
			// node found, remove it from the list
			// remove references to this node from the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				delete(c.OutputAddress, port.Address)
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				delete(c.InputAddress, port.Address)
			}
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
		}
	}

	return fmt.Errorf("no known node with this ip known, ip: %s", node.IP)
}

// gcNode will remove stale Nodes from the list of known nodes
// it will loop through the list of nodes and remove nodes older then X seconds
func (c *Controller) gcNode() {
	c.nodeLock.Lock()
	defer c.nodeLock.Unlock()

	// nodes are stale after 5 missed ArtPoll's
	//staleAfter, _ := time.ParseDuration(fmt.Sprintf("%ds", 5*pollInterval))
	staleAfter := 7 * time.Second

start:
	for i := range c.Nodes {
		if c.Nodes[i].LastSeen.Add(staleAfter).Before(time.Now()) {
			// it has been more then X seconds since we saw this node. remove it now.
			c.log.With(Fields{"node": c.Nodes[i].Node.Name, "ip": c.Nodes[i].Node.IP.String()}).Debug("remove stale node")

			// remove references to this node from the output map
			for _, port := range c.Nodes[i].Node.OutputPorts {
				delete(c.OutputAddress, port.Address)
			}
			for _, port := range c.Nodes[i].Node.InputPorts {
				delete(c.InputAddress, port.Address)
			}
			// remove node
			c.Nodes = append(c.Nodes[:i], c.Nodes[i+1:]...)
			goto start
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package statfs

import (
	"context"
	"os"
	"sync"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/fuseutil"
)

// A file system that allows orchestrating canned responses to statfs ops, for
// testng out OS-specific statfs behavior.
//
// The file system allows opening and writing to any name that is a child of
// the root inode, and keeps track of the most recent write size delivered by
// the kernel (in order to test statfs response block size effects on write
// size, if any).
//
// Safe for concurrent access.
type FS interface {
	fuseutil.FileSystem

	// Set the canned response to be used for future statfs ops.
	SetStatFSResponse(r fuseops.StatFSOp)

	// Set the canned response to be used for future stat ops.
	SetStatResponse(r fuseops.InodeAttributes)

	// Return the size of the most recent write delivered by the kernel, or -1 if
	// none.
	MostRecentWriteSize() int
}

func New() FS {
	return &statFS{
		cannedStatResponse: fuseops.InodeAttributes{
			Mode: 0666,
		},
		mostRecentWriteSize: -1,
	}
}

const childInodeID = fuseops.RootInodeID + 1

type statFS struct {
	fuseutil.NotImplementedFileSystem

	mu                  sync.Mutex
	cannedResponse      fuseops.StatFSOp        // GUARDED_BY(mu)
	cannedStatResponse  fuseops.InodeAttributes // GUARDED_BY(mu)
	mostRecentWriteSize int                     // GUARDED_BY(mu)
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func dirAttrs() fuseops.InodeAttributes {
	return fuseops.InodeAttributes{
		Mode: os.ModeDir | 0777,
	}
}

func (fs *statFS) fileAttrs() fuseops.InodeAttributes {
	return fs.cannedStatResponse
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) SetStatFSResponse(r fuseops.StatFSOp) {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	fs.cannedResponse = r
}

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) SetStatResponse(r fuseops.InodeAttributes) {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	fs.cannedStatResponse = r
}

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) MostRecentWriteSize() int {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	return fs.mostRecentWriteSize
}

////////////////////////////////////////////////////////////////////////
// FileSystem methods
////////////////////////////////////////////////////////////////////////

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) StatFS(
	ctx context.Context,
	op *fuseops.StatFSOp) error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	*op = fs.cannedResponse
	return nil
}

func (fs *statFS) LookUpInode(
	ctx context.Context,
	op *fuseops.LookUpInodeOp) error {
	// Only the root has children.
	if op.Parent != fuseops.RootInodeID {
		return fuse.ENOENT
	}

	op.Entry.Child = childInodeID
	op.Entry.Attributes = fs.fileAttrs()

	return nil
}

func (fs *statFS) GetInodeAttributes(
	ctx context.Context,
	op *fuseops.GetInodeAttributesOp) error {
	switch op.Inode {
	case fuseops.RootInodeID:
		op.Attributes = dirAttrs()

	case childInodeID:
		op.Attributes = fs.fileAttrs()

	default:
		return fuse.ENOENT
	}

	return nil
}

func (fs *statFS) SetInodeAttributes(
	ctx context.Context,
	op *fuseops.SetInodeAttributesOp) error {
	// Ignore calls to truncate existing files when opening.
	return nil
}

func (fs *statFS) OpenFile(
	ctx context.Context,
	op *fuseops.OpenFileOp) error {
	return nil
}

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) WriteFile(
	ctx context.Context,
	op *fuseops.WriteFileOp) error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	fs.mostRecentWriteSize = len(op.Data)
	return nil
}
<commit_msg>statfs sample: add missing locking<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package statfs

import (
	"context"
	"os"
	"sync"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/fuseutil"
)

// A file system that allows orchestrating canned responses to statfs ops, for
// testng out OS-specific statfs behavior.
//
// The file system allows opening and writing to any name that is a child of
// the root inode, and keeps track of the most recent write size delivered by
// the kernel (in order to test statfs response block size effects on write
// size, if any).
//
// Safe for concurrent access.
type FS interface {
	fuseutil.FileSystem

	// Set the canned response to be used for future statfs ops.
	SetStatFSResponse(r fuseops.StatFSOp)

	// Set the canned response to be used for future stat ops.
	SetStatResponse(r fuseops.InodeAttributes)

	// Return the size of the most recent write delivered by the kernel, or -1 if
	// none.
	MostRecentWriteSize() int
}

func New() FS {
	return &statFS{
		cannedStatResponse: fuseops.InodeAttributes{
			Mode: 0666,
		},
		mostRecentWriteSize: -1,
	}
}

const childInodeID = fuseops.RootInodeID + 1

type statFS struct {
	fuseutil.NotImplementedFileSystem

	mu                  sync.Mutex
	cannedResponse      fuseops.StatFSOp        // GUARDED_BY(mu)
	cannedStatResponse  fuseops.InodeAttributes // GUARDED_BY(mu)
	mostRecentWriteSize int                     // GUARDED_BY(mu)
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func dirAttrs() fuseops.InodeAttributes {
	return fuseops.InodeAttributes{
		Mode: os.ModeDir | 0777,
	}
}

func (fs *statFS) fileAttrs() fuseops.InodeAttributes {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	return fs.cannedStatResponse
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) SetStatFSResponse(r fuseops.StatFSOp) {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	fs.cannedResponse = r
}

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) SetStatResponse(r fuseops.InodeAttributes) {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	fs.cannedStatResponse = r
}

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) MostRecentWriteSize() int {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	return fs.mostRecentWriteSize
}

////////////////////////////////////////////////////////////////////////
// FileSystem methods
////////////////////////////////////////////////////////////////////////

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) StatFS(
	ctx context.Context,
	op *fuseops.StatFSOp) error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	*op = fs.cannedResponse
	return nil
}

func (fs *statFS) LookUpInode(
	ctx context.Context,
	op *fuseops.LookUpInodeOp) error {
	// Only the root has children.
	if op.Parent != fuseops.RootInodeID {
		return fuse.ENOENT
	}

	op.Entry.Child = childInodeID
	op.Entry.Attributes = fs.fileAttrs()

	return nil
}

func (fs *statFS) GetInodeAttributes(
	ctx context.Context,
	op *fuseops.GetInodeAttributesOp) error {
	switch op.Inode {
	case fuseops.RootInodeID:
		op.Attributes = dirAttrs()

	case childInodeID:
		op.Attributes = fs.fileAttrs()

	default:
		return fuse.ENOENT
	}

	return nil
}

func (fs *statFS) SetInodeAttributes(
	ctx context.Context,
	op *fuseops.SetInodeAttributesOp) error {
	// Ignore calls to truncate existing files when opening.
	return nil
}

func (fs *statFS) OpenFile(
	ctx context.Context,
	op *fuseops.OpenFileOp) error {
	return nil
}

// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) WriteFile(
	ctx context.Context,
	op *fuseops.WriteFileOp) error {
	fs.mu.Lock()
	defer fs.mu.Unlock()

	fs.mostRecentWriteSize = len(op.Data)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/evolsnow/robot/conn"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"
)

func (rb *Robot) Start(update tgbotapi.Update) string {
	user := update.Message.Chat.UserName
	go conn.SetUserChatId(user, update.Message.Chat.ID)
	return "welcome: " + user
}

func (rb *Robot) Help(update tgbotapi.Update) string {
	helpMsg := `
/alarm - set a reminder
/evolve	- self evolution of me
/movie - find movie download links
/trans - translate words between english and chinese
/help - show this help message
`
	return helpMsg
}

func (rb *Robot) Evolve(update tgbotapi.Update) {
	if update.Message.Chat.FirstName != "Evol" || update.Message.Chat.LastName != "Gan" {
		rb.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "sorry, unauthorized"))
		return
	}
	<-saidGoodBye
	close(saidGoodBye)
	cmd := exec.Command("bash", "/root/evolve_"+rb.nickName)
	cmd.Start()
	os.Exit(1)
}

func (rb *Robot) Translate(update tgbotapi.Update) string {
	var info string
	if string(update.Message.Text[0]) == "/" {
		raw := strings.SplitAfterN(update.Message.Text, " ", 2)
		if len(raw) < 2 {
			return "what do you want to translate, try '/trans cat'?"
		} else {
			info = "翻译" + raw[1]
		}
	} else {
		info = update.Message.Text
	}

	return qinAI(info)

}
func (rb *Robot) Talk(update tgbotapi.Update) string {
	info := update.Message.Text
	if strings.Contains(info, rb.name) {
		if strings.Contains(info, "闭嘴") || strings.Contains(info, "别说话") {
			rb.shutUp = true
		} else if rb.shutUp && strings.Contains(info, "说话") {
			rb.shutUp = false
			return fmt.Sprintf("%s终于可以说话啦", rb.nickName)
		}
		info = strings.Replace(info, fmt.Sprintf("@%s", rb.name), "", -1)
	}

	if rb.shutUp {
		return ""
	}
	log.Printf(info)

	if rb.nickName == "samaritan" {
		if chinese(info) {
			return tlAI(info)
		} else {
			return mitAI(info)
		}
	} else { //jarvis use another AI
		return qinAI(info)
	}
	//	return response
}

func (rb *Robot) SetReminder(update tgbotapi.Update, step int) string {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpTask := userTask[user]
		tmpTask.ChatId = update.Message.Chat.ID
		tmpTask.Owner = update.Message.Chat.UserName
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "Ok, what should I remind you to do?"
	case 1:
		//save thing
		tmpTask := userTask[user]
		tmpTask.Desc = update.Message.Text
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "When or how much time after?\n" +
			"You can type:\n" +
			"'*2/14 11:30*' means 11:30 at 2/14 \n" + //first format
			"'*11:30*' means  11:30 today\n" + //second format
			"'*5m10s*' means 5 minutes 10 seconds later" //third format
	case 2:
		defer delete(userAction, user)
		//save time duration
		text := update.Message.Text
		text = strings.Replace(text, "：", ":", -1)
		firstFormat := "1/02 15:04"
		secondFormat := "15:04"
		thirdFormat := "15:04:05"
		var showTime string
		var scheduledTime time.Time
		var nowTime = time.Now()
		var du time.Duration
		var err error
		if strings.Contains(text, ":") {
			scheduledTime, err = time.Parse(firstFormat, text)
			nowTime, _ = time.Parse(firstFormat, nowTime.Format(firstFormat))
			showTime = scheduledTime.Format(firstFormat)
			if err != nil { //try to parse with first format
				scheduledTime, err = time.Parse(secondFormat, text)
				nowTime, _ = time.Parse(secondFormat, nowTime.Format(secondFormat))
				showTime = scheduledTime.Format(secondFormat)
				if err != nil {
					return "wrong format, try '2/14 11:30' or '11:30'?"
				}
			}
			du = scheduledTime.Sub(nowTime)
		} else {

			du, err = time.ParseDuration(text)
			scheduledTime = nowTime.Add(du)
			showTime = scheduledTime.Format(thirdFormat)
			if err != nil {
				return "wrong format, try '1h2m3s'?"
			}
		}
		//		tmpTask := userTask[user]
		//		tmpTask.When = scheduledTime
		//		userTask[user] = tmpTask
		go func(rb *Robot, ts Task) {
			timer := time.NewTimer(du)
			rawMsg := fmt.Sprintf("Hi %s, maybe it's time to:\n*%s*", ts.Owner, ts.Desc)
			msg := tgbotapi.NewMessage(ts.ChatId, rawMsg)
			msg.ParseMode = "markdown"
			<-timer.C
			_, err := rb.bot.Send(msg)
			if err != nil {
				rb.bot.Send(tgbotapi.NewMessage(conn.GetUserChatId(ts.Owner), rawMsg))
			}
			delete(userTask, user)
		}(rb, userTask[user])

		//		delete(userAction, user)
		return fmt.Sprintf("Ok, I will remind you that\n*%s* - *%s*", showTime, userTask[user].Desc)
	}
	return ""
}

func (rb *Robot) DownloadMovie(update tgbotapi.Update, step int, results chan string) (ret string) {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		ret = "Ok, which movie do you want to download?"
	case 1:
		defer func() {
			delete(userAction, user)
			results <- "done"
		}()
		results <- "Searching..."
		movie := update.Message.Text
		var wg sync.WaitGroup
		wg.Add(2)
		go getMovieFromZmz(movie, results, &wg)
		go getMovieFromLbl(movie, results, &wg)
		wg.Wait()
	}
	return
}

func getMovieFromZmz(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	return
}

func getMovieFromLbl(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	var id string
	resp, _ := http.Get("http://www.lbldy.com/search/" + movie)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	re, _ := regexp.Compile("
")
	//find first match case
	firstId := re.FindSubmatch(body)
	if len(firstId) == 0 {
		results <- fmt.Sprintf("no answer for *%s* from lbl", movie)
		return
	} else {
		id = string(firstId[1])
		resp, _ = http.Get("http://www.lbldy.com/movie/" + id + ".html")
		defer resp.Body.Close()
		re, _ = regexp.Compile(`<p><a href="(.*?)"( target="_blank">|>)(.*?)</a></p>`)
		body, _ := ioutil.ReadAll(resp.Body)
		//go does not support (?!)
		body = []byte(strings.Replace(string(body), `<a href="/xunlei/"`, "", -1))
		downloads := re.FindAllSubmatch(body, -1)
		if len(downloads) == 0 {
			results <- fmt.Sprintf("no answer for *%s* from lbl", movie)
			return
		} else {
			results <- "Results from lbl:\n\n"
			ret := ""
			for i := range downloads {
				ret += fmt.Sprintf("*%s*\n%s\n\n", string(downloads[i][3]), string(downloads[i][1]))
			}
			results <- ret
		}
	}
}
<commit_msg>code markdown<commit_after>package main

import (
	"fmt"
	"github.com/evolsnow/robot/conn"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"sync"
	"time"
)

func (rb *Robot) Start(update tgbotapi.Update) string {
	user := update.Message.Chat.UserName
	go conn.SetUserChatId(user, update.Message.Chat.ID)
	return "welcome: " + user
}

func (rb *Robot) Help(update tgbotapi.Update) string {
	helpMsg := `
/alarm - set a reminder
/evolve	- self evolution of me
/movie - find movie download links
/trans - translate words between english and chinese
/help - show this help message
`
	return helpMsg
}

func (rb *Robot) Evolve(update tgbotapi.Update) {
	if update.Message.Chat.FirstName != "Evol" || update.Message.Chat.LastName != "Gan" {
		rb.bot.Send(tgbotapi.NewMessage(update.Message.Chat.ID, "sorry, unauthorized"))
		return
	}
	<-saidGoodBye
	close(saidGoodBye)
	cmd := exec.Command("bash", "/root/evolve_"+rb.nickName)
	cmd.Start()
	os.Exit(1)
}

func (rb *Robot) Translate(update tgbotapi.Update) string {
	var info string
	if string(update.Message.Text[0]) == "/" {
		raw := strings.SplitAfterN(update.Message.Text, " ", 2)
		if len(raw) < 2 {
			return "what do you want to translate, try '/trans cat'?"
		} else {
			info = "翻译" + raw[1]
		}
	} else {
		info = update.Message.Text
	}

	return qinAI(info)

}
func (rb *Robot) Talk(update tgbotapi.Update) string {
	info := update.Message.Text
	if strings.Contains(info, rb.name) {
		if strings.Contains(info, "闭嘴") || strings.Contains(info, "别说话") {
			rb.shutUp = true
		} else if rb.shutUp && strings.Contains(info, "说话") {
			rb.shutUp = false
			return fmt.Sprintf("%s终于可以说话啦", rb.nickName)
		}
		info = strings.Replace(info, fmt.Sprintf("@%s", rb.name), "", -1)
	}

	if rb.shutUp {
		return ""
	}
	log.Printf(info)

	if rb.nickName == "samaritan" {
		if chinese(info) {
			return tlAI(info)
		} else {
			return mitAI(info)
		}
	} else { //jarvis use another AI
		return qinAI(info)
	}
	//	return response
}

func (rb *Robot) SetReminder(update tgbotapi.Update, step int) string {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpTask := userTask[user]
		tmpTask.ChatId = update.Message.Chat.ID
		tmpTask.Owner = update.Message.Chat.UserName
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "Ok, what should I remind you to do?"
	case 1:
		//save thing
		tmpTask := userTask[user]
		tmpTask.Desc = update.Message.Text
		userTask[user] = tmpTask

		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		return "When or how much time after?\n" +
			"You can type:\n" +
			"'*2/14 11:30*' means 11:30 at 2/14 \n" + //first format
			"'*11:30*' means  11:30 today\n" + //second format
			"'*5m10s*' means 5 minutes 10 seconds later" //third format
	case 2:
		defer delete(userAction, user)
		//save time duration
		text := update.Message.Text
		text = strings.Replace(text, "：", ":", -1)
		firstFormat := "1/02 15:04"
		secondFormat := "15:04"
		thirdFormat := "15:04:05"
		var showTime string
		var scheduledTime time.Time
		var nowTime = time.Now()
		var du time.Duration
		var err error
		if strings.Contains(text, ":") {
			scheduledTime, err = time.Parse(firstFormat, text)
			nowTime, _ = time.Parse(firstFormat, nowTime.Format(firstFormat))
			showTime = scheduledTime.Format(firstFormat)
			if err != nil { //try to parse with first format
				scheduledTime, err = time.Parse(secondFormat, text)
				nowTime, _ = time.Parse(secondFormat, nowTime.Format(secondFormat))
				showTime = scheduledTime.Format(secondFormat)
				if err != nil {
					return "wrong format, try '2/14 11:30' or '11:30'?"
				}
			}
			du = scheduledTime.Sub(nowTime)
		} else {

			du, err = time.ParseDuration(text)
			scheduledTime = nowTime.Add(du)
			showTime = scheduledTime.Format(thirdFormat)
			if err != nil {
				return "wrong format, try '1h2m3s'?"
			}
		}
		//		tmpTask := userTask[user]
		//		tmpTask.When = scheduledTime
		//		userTask[user] = tmpTask
		go func(rb *Robot, ts Task) {
			timer := time.NewTimer(du)
			rawMsg := fmt.Sprintf("Hi %s, maybe it's time to:\n*%s*", ts.Owner, ts.Desc)
			msg := tgbotapi.NewMessage(ts.ChatId, rawMsg)
			msg.ParseMode = "markdown"
			<-timer.C
			_, err := rb.bot.Send(msg)
			if err != nil {
				rb.bot.Send(tgbotapi.NewMessage(conn.GetUserChatId(ts.Owner), rawMsg))
			}
			delete(userTask, user)
		}(rb, userTask[user])

		//		delete(userAction, user)
		return fmt.Sprintf("Ok, I will remind you that\n*%s* - *%s*", showTime, userTask[user].Desc)
	}
	return ""
}

func (rb *Robot) DownloadMovie(update tgbotapi.Update, step int, results chan string) (ret string) {
	user := update.Message.Chat.UserName
	switch step {
	case 0:
		//known issue of go, you can not just assign update.Message.Chat.ID to userTask[user].ChatId
		tmpAction := userAction[user]
		tmpAction.ActionStep++
		userAction[user] = tmpAction
		ret = "Ok, which movie do you want to download?"
	case 1:
		defer func() {
			delete(userAction, user)
			results <- "done"
		}()
		results <- "Searching..."
		movie := update.Message.Text
		var wg sync.WaitGroup
		wg.Add(2)
		go getMovieFromZmz(movie, results, &wg)
		go getMovieFromLbl(movie, results, &wg)
		wg.Wait()
	}
	return
}

func getMovieFromZmz(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	return
}

func getMovieFromLbl(movie string, results chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	var id string
	resp, _ := http.Get("http://www.lbldy.com/search/" + movie)
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	re, _ := regexp.Compile("
")
	//find first match case
	firstId := re.FindSubmatch(body)
	if len(firstId) == 0 {
		results <- fmt.Sprintf("no answer for *%s* from lbl", movie)
		return
	} else {
		id = string(firstId[1])
		resp, _ = http.Get("http://www.lbldy.com/movie/" + id + ".html")
		defer resp.Body.Close()
		re, _ = regexp.Compile(`<p><a href="(.*?)"( target="_blank">|>)(.*?)</a></p>`)
		body, _ := ioutil.ReadAll(resp.Body)
		//go does not support (?!)
		body = []byte(strings.Replace(string(body), `<a href="/xunlei/"`, "", -1))
		downloads := re.FindAllSubmatch(body, -1)
		if len(downloads) == 0 {
			results <- fmt.Sprintf("no answer for *%s* from lbl", movie)
			return
		} else {
			results <- "Results from lbl:\n\n"
			ret := ""
			for i := range downloads {
				ret += fmt.Sprintf("*%s*\n```%s```\n\n", string(downloads[i][3]), string(downloads[i][1]))
			}
			results <- ret
		}
	}
}
<|endoftext|>


<commit_before>package fire

import (
	"errors"

	"gopkg.in/mgo.v2/bson"
)

// A Callback can be an Authorizer or Validator an is called during accessing a
// resource via the API.
//
// Note: If the callback returns an error wrapped using Fatal() the API returns
// an InternalServerError status and the error will be logged. All other errors
// are serialized to an error object and returned.
type Callback func(*Context) error

type fatalError struct {
	err error
}

// Fatal wraps an error and marks it as fatal.
func Fatal(err error) error {
	return &fatalError{
		err: err,
	}
}

func (err *fatalError) Error() string {
	return "fatal: " + err.err.Error()
}

func isFatal(err error) bool {
	_, ok := err.(*fatalError)
	return ok
}

// Combine combines multiple callbacks to one.
//
// Note: Execution will be stopped if a callback returns and error.
func Combine(callbacks ...Callback) Callback {
	return func(ctx *Context) error {
		// call all callbacks
		for _, cb := range callbacks {
			err := cb(ctx)
			if err != nil {
				return err
			}
		}

		return nil
	}
}

// DependentResourcesValidator counts documents in the supplied collections
// and returns an error if some get found. This callback is meant to protect
// resources from breaking relations when requested to be deleted.
//
// Resources are defined by passing pairs of collections and fields where the
// field must be a database field of the target resource model:
//
//		DependentResourcesValidator(M{
// 			"posts": "user_id",
//			"comments": "user_id",
// 		})
//
func DependentResourcesValidator(resources Map) Callback {
	return func(ctx *Context) error {
		// only run validator on Delete
		if ctx.Action != Delete {
			return nil
		}

		// check all relations
		for coll, field := range resources {
			// count referencing documents
			n, err := ctx.DB.C(coll).Find(bson.M{field.(string): ctx.Query["_id"]}).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// immediately return if a document is found
			if n == 1 {
				return errors.New("resource has dependent resources")
			}
		}

		// pass validation
		return nil
	}
}

// VerifyReferencesValidator makes sure all references in the document are
// existing by counting on the related collections.
//
// References are defined by passing pairs of fields and collections where the
// field must be a database field on the resource model:
//
//		VerifyReferencesValidator(M{
// 			"post_id": "posts",
//			"user_id": "users",
// 		})
//
func VerifyReferencesValidator(references Map) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// check all references
		for field, collection := range references {
			// read referenced id
			id := ctx.Model.Get(field)
			if id == nil {
				continue
			}

			// count entities in database
			n, err := ctx.DB.C(collection.(string)).FindId(id).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// check for existence
			if n != 1 {
				return errors.New("missing required relationship " + field)
			}
		}

		// pass validation
		return nil
	}
}

// MatchingReferencesValidator compares the model with a related model and
// checks if certain references are shared.
//
// The target model is defined by passing its collection and the referencing
// field on the current model. The matcher is defined by passing pairs of
// database fields on the target and current model:
//
//		MatchingReferencesValidator("posts", "post_id", M{
// 			"user_id": "user_id",
// 		})
//
func MatchingReferencesValidator(collection, reference string, matcher Map) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// get main reference
		id := ctx.Model.Get(reference)
		if id == nil {
			// continue if relation is not set
			return nil
		}

		// prepare query
		query := bson.M{
			"_id": id,
		}

		// add other references
		for targetField, modelField := range matcher {
			id := ctx.Model.Get(modelField.(string))
			if id == nil {
				return errors.New("missing id")
			}

			query[targetField] = id
		}

		// query db
		n, err := ctx.DB.C(collection).Find(query).Limit(1).Count()
		if err != nil {
			return Fatal(err)
		}

		// return error if document is missing
		if n == 0 {
			return errors.New("references do not match")
		}

		return nil
	}
}
<commit_msg>fixed validators<commit_after>package fire

import (
	"errors"

	"gopkg.in/mgo.v2/bson"
)

// A Callback can be an Authorizer or Validator an is called during accessing a
// resource via the API.
//
// Note: If the callback returns an error wrapped using Fatal() the API returns
// an InternalServerError status and the error will be logged. All other errors
// are serialized to an error object and returned.
type Callback func(*Context) error

type fatalError struct {
	err error
}

// Fatal wraps an error and marks it as fatal.
func Fatal(err error) error {
	return &fatalError{
		err: err,
	}
}

func (err *fatalError) Error() string {
	return "fatal: " + err.err.Error()
}

func isFatal(err error) bool {
	_, ok := err.(*fatalError)
	return ok
}

// Combine combines multiple callbacks to one.
//
// Note: Execution will be stopped if a callback returns and error.
func Combine(callbacks ...Callback) Callback {
	return func(ctx *Context) error {
		// call all callbacks
		for _, cb := range callbacks {
			err := cb(ctx)
			if err != nil {
				return err
			}
		}

		return nil
	}
}

// DependentResourcesValidator counts documents in the supplied collections
// and returns an error if some get found. This callback is meant to protect
// resources from breaking relations when requested to be deleted.
//
// Resources are defined by passing pairs of collections and fields where the
// field must be a database field of the target resource model:
//
//		DependentResourcesValidator(M{
// 			"posts": "user_id",
//			"comments": "user_id",
// 		})
//
func DependentResourcesValidator(resources Map) Callback {
	return func(ctx *Context) error {
		// only run validator on Delete
		if ctx.Action != Delete {
			return nil
		}

		// check all relations
		for coll, field := range resources {
			// count referencing documents
			n, err := ctx.DB.C(coll).Find(bson.M{field.(string): ctx.Query["_id"]}).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// immediately return if a document is found
			if n == 1 {
				return errors.New("Resource has dependent resources")
			}
		}

		// pass validation
		return nil
	}
}

// VerifyReferencesValidator makes sure all references in the document are
// existing by counting on the related collections.
//
// References are defined by passing pairs of fields and collections where the
// field must be a database field on the resource model:
//
//		VerifyReferencesValidator(M{
// 			"post_id": "posts",
//			"user_id": "users",
// 		})
//
func VerifyReferencesValidator(references Map) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// check all references
		for field, collection := range references {
			// read referenced id
			id := ctx.Model.Get(field)

			// continue if reference is not set
			if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
				continue
			}

			// count entities in database
			n, err := ctx.DB.C(collection.(string)).FindId(id).Limit(1).Count()
			if err != nil {
				return Fatal(err)
			}

			// check for existence
			if n != 1 {
				return errors.New("Missing required relationship " + field)
			}
		}

		// pass validation
		return nil
	}
}

// MatchingReferencesValidator compares the model with a related model and
// checks if certain references are shared.
//
// The target model is defined by passing its collection and the referencing
// field on the current model. The matcher is defined by passing pairs of
// database fields on the target and current model:
//
//		MatchingReferencesValidator("posts", "post_id", M{
// 			"user_id": "user_id",
// 		})
//
func MatchingReferencesValidator(collection, reference string, matcher Map) Callback {
	return func(ctx *Context) error {
		// only run validator on Create and Update
		if ctx.Action != Create && ctx.Action != Update {
			return nil
		}

		// get main reference
		id := ctx.Model.Get(reference)

		// continue if reference is not set
		if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
			return nil
		}

		// prepare query
		query := bson.M{
			"_id": id,
		}

		// add other references
		for targetField, modelField := range matcher {
			id := ctx.Model.Get(modelField.(string))

			// abort if reference is missing
			if oid, ok := id.(*bson.ObjectId); ok && oid == nil {
				return errors.New("Missing ID")
			}

			query[targetField] = id
		}

		// query db
		n, err := ctx.DB.C(collection).Find(query).Limit(1).Count()
		if err != nil {
			return Fatal(err)
		}

		// return error if document is missing
		if n == 0 {
			return errors.New("References do not match")
		}

		return nil
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"

type automobile struct {
	make  string
	model string
	year  int
	miles float64
	color string
}

// In Go methods reciever can be either a value of type or pointer to value.
// If we call a method that takes a value, on a pointer, Go is smart enough to
// dereference the pointer and pass the underlying value as the method’s receiver.

// NOTE: The rule about pointers vs. values for receivers is that value methods can be
// invoked on pointers and values, but pointer methods can only be invoked on pointers.
// This rule arises because pointer methods can modify the receiver; invoking
// them on a value would cause the method to receive a copy of the value, so any
// modifications would be discarded. The language therefore disallows this mistake.
// There is a handy exception, though. When the value is addressable (i.e., it is a
// variable, a dereferenced pointer, an array or slice item, or an addressable field
// in a struct), the language  takes care of the common case of invoking a pointer
// method on a value by inserting the address operator automatically.

// NOTE: interface methods are not addressable, hence the above exception will not
// apply if the method(s) of a type satisfy an interface. see the topic intrefaces
// for more relavent details.

// The methods start, stop and honk work either way. It only reads a. It does'nt
// wether it is reading the original value (through a pointer) or a copy of the value
func (a automobile) start() {
	fmt.Printf("%s %s has started and is ready to go\n", a.make, a.model)
}

func (a automobile) stop() {
	fmt.Printf("%s %s has come to stop\n", a.make, a.model)

}

func (a automobile) honk() {
	fmt.Printf("%s %s is honking its horn\n", a.make, a.model)
}

// The paint method has no effect when a is automobile. paint mutates a. When a is
// a value (non-pointer) type, the method sees a copy of automobile and cannot mutate
// the original value
func (a automobile) paint(c string) {
	a.color = c
	fmt.Printf("%s %s has been painted %s\n", a.make, a.model, c)
}

// There are two reasons to use a pointer receiver. First, to avoid copying the
// value on each method call (more efficient if the value type is a large struct).
// Second, so that the method can modify the value that its receiver points to.
func (a *automobile) resetMiles() {
	a.miles = 0.0
}

func main() {
	fusion := automobile{"Ford", "Fusion", 2014, 44.0, "White"}
	mustang := automobile{"Ford", "Mustang", 2010, 12378.45, "Red"}

	fusion.start()
	fusion.honk()
	fusion.stop()

	mustang.start()
	mustang.honk()
	mustang.stop()

	fmt.Printf("%s %s is of %s color\n", mustang.make, mustang.model, mustang.color)
	// mutate mustang
	mustang.paint("Black")
	// since paint methods reciever type is defined as value of automobile
	// the paint method is pass a copy of automobile, which it mutates. But the
	// original value is left unchnaged
	fmt.Printf("The color of %s %s is %s\n", mustang.make, mustang.model, mustang.color)

	fmt.Printf("%s %s has %.2f miles on it\n", mustang.make, mustang.model, mustang.miles)
	// since the value mustang is addressable Go would automatically insert (prefeix)
	// the address operator to the value mustang, hence the following two lines
	// are equivalent
	mustang.resetMiles()
	//(&mustang).resetMiles() // same as previus method call
	fmt.Printf("%s %s has %.2f miles on it\n", mustang.make, mustang.model, mustang.miles)
}
<commit_msg>fix typos<commit_after>package main

import "fmt"

type automobile struct {
	make  string
	model string
	year  int
	miles float64
	color string
}

// In Go methods reciever can be either a value of type or pointer to value.
// If we call a method that takes a value, on a pointer, Go is smart enough to
// dereference the pointer and pass the underlying value as the method’s receiver.

// NOTE: The rule about pointers vs. values for receivers is that value methods can be
// invoked on pointers and values, but pointer methods can only be invoked on pointers.
// This rule arises because pointer methods can modify the receiver; invoking
// them on a value would cause the method to receive a copy of the value, so any
// modifications would be discarded. The language therefore disallows this mistake.
// There is a handy exception, though. When the value is addressable (i.e., it is a
// variable, a dereferenced pointer, an array or slice item, or an addressable field
// in a struct), the language  takes care of the common case of invoking a pointer
// method on a value by inserting the address operator automatically.

// NOTE: interface methods and map values are not addressable, hence the above
// exception will not apply if the method(s) of a type satisfies an interface.
// see the topic intrefaces for more relavent details.

// The methods start, stop and honk work either way. It only reads a. It does'nt
// wether it is reading the original value (through a pointer) or a copy of the value
func (a automobile) start() {
	fmt.Printf("%s %s has started and is ready to go\n", a.make, a.model)
}

func (a automobile) stop() {
	fmt.Printf("%s %s has come to stop\n", a.make, a.model)

}

func (a automobile) honk() {
	fmt.Printf("%s %s is honking its horn\n", a.make, a.model)
}

// The paint method has no effect when a is automobile. paint mutates a. When a is
// a value (non-pointer) type, the method sees a copy of automobile and cannot mutate
// the original value
func (a automobile) paint(c string) {
	a.color = c
	fmt.Printf("%s %s has been painted %s\n", a.make, a.model, c)
}

// There are two reasons to use a pointer receiver. First, to avoid copying the
// value on each method call (more efficient if the value type is a large struct).
// Second, so that the method can modify the value that its receiver points to.
func (a *automobile) resetMiles() {
	a.miles = 0.0
}

func main() {
	fusion := automobile{"Ford", "Fusion", 2014, 44.0, "White"}
	mustang := automobile{"Ford", "Mustang", 2010, 12378.45, "Red"}

	fusion.start()
	fusion.honk()
	fusion.stop()

	mustang.start()
	mustang.honk()
	mustang.stop()

	fmt.Printf("%s %s is of %s color\n", mustang.make, mustang.model, mustang.color)
	// mutate mustang
	mustang.paint("Black")
	// since paint methods reciever type is defined as value of automobile
	// the paint method is pass a copy of automobile, which it mutates. But the
	// original value is left unchnaged
	fmt.Printf("The color of %s %s is %s\n", mustang.make, mustang.model, mustang.color)

	fmt.Printf("%s %s has %.2f miles on it\n", mustang.make, mustang.model, mustang.miles)
	// since the value mustang is addressable Go would automatically insert (prefeix)
	// the address operator to the value mustang, hence the following two lines
	// are equivalent
	mustang.resetMiles()
	//(&mustang).resetMiles() // same as previus method call
	fmt.Printf("%s %s has %.2f miles on it\n", mustang.make, mustang.model, mustang.miles)
}
<|endoftext|>


<commit_before>package admin

import (
	"regexp"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor"
)

type scopeFunc func(db *gorm.DB, context *qor.Context) *gorm.DB

type Searcher struct {
	*Context
	scopes  []*Scope
	filters map[string]string
}

func (s *Searcher) Scope(names ...string) *Searcher {
	for _, name := range names {
		if scope := s.Resource.scopes[name]; scope != nil && !scope.Default {
			s.scopes = append(s.scopes, scope)
		}
	}
	return s
}

func (s *Searcher) Filter(name, query string) *Searcher {
	if s.filters == nil {
		s.filters = map[string]string{}
	}
	s.filters[name] = query
	return s
}

var filterRegexp = regexp.MustCompile(`^filters\[(.*?)\]$`)

func (s *Searcher) callScopes(context *qor.Context) *qor.Context {
	db := context.GetDB()

	// call default scopes
	for _, scope := range s.Resource.scopes {
		if scope.Default {
			db = scope.Handle(db, context)
		}
	}

	// call scopes
	for _, scope := range s.scopes {
		db = scope.Handle(db, context)
	}

	// call filters
	if s.filters != nil {
		for key, value := range s.filters {
			filter := s.Resource.filters[key]
			if filter != nil && filter.Handler != nil {
				db = filter.Handler(key, value, db, context)
			} else {
				db = DefaultHandler(key, value, db, context)
			}
		}
	}
	context.SetDB(db)
	return context
}

func (s *Searcher) getContext() *qor.Context {
	return s.Context.Context.New()
}

func (s *Searcher) parseContext() *qor.Context {
	var context = s.getContext()

	if context != nil && context.Request != nil {
		// parse scopes
		scopes := strings.Split(context.Request.Form.Get("scopes"), "|")
		s.Scope(scopes...)

		// parse filters
		for key, value := range context.Request.Form {
			if matches := filterRegexp.FindStringSubmatch(key); len(matches) > 0 {
				s.Filter(matches[1], value[0])
			}
		}
	}

	s.callScopes(context)

	return context
}

func (s *Searcher) FindAll() (interface{}, error) {
	context := s.parseContext()
	result := s.Resource.NewSlice()
	err := s.Resource.CallSearcher(result, context)
	return result, err
}

func (s *Searcher) FindOne() (interface{}, error) {
	context := s.parseContext()
	result := s.Resource.NewStruct()
	err := s.Resource.CallFinder(result, nil, context)
	return result, err
}
<commit_msg>Don't pollute searcher<commit_after>package admin

import (
	"regexp"
	"strings"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor"
)

type scopeFunc func(db *gorm.DB, context *qor.Context) *gorm.DB

type Searcher struct {
	*Context
	scopes  []*Scope
	filters map[string]string
}

func (s *Searcher) clone() *Searcher {
	return &Searcher{Context: s.Context, scopes: s.scopes, filters: s.filters}
}

func (s *Searcher) Scope(names ...string) *Searcher {
	newSearcher := s.clone()
	for _, name := range names {
		if scope := s.Resource.scopes[name]; scope != nil && !scope.Default {
			newSearcher.scopes = append(s.scopes, scope)
		}
	}
	return newSearcher
}

func (s *Searcher) Filter(name, query string) *Searcher {
	newSearcher := s.clone()
	if newSearcher.filters == nil {
		newSearcher.filters = map[string]string{}
	}
	newSearcher.filters[name] = query
	return newSearcher
}

var filterRegexp = regexp.MustCompile(`^filters\[(.*?)\]$`)

func (s *Searcher) callScopes(context *qor.Context) *qor.Context {
	db := context.GetDB()

	// call default scopes
	for _, scope := range s.Resource.scopes {
		if scope.Default {
			db = scope.Handle(db, context)
		}
	}

	// call scopes
	for _, scope := range s.scopes {
		db = scope.Handle(db, context)
	}

	// call filters
	if s.filters != nil {
		for key, value := range s.filters {
			filter := s.Resource.filters[key]
			if filter != nil && filter.Handler != nil {
				db = filter.Handler(key, value, db, context)
			} else {
				db = DefaultHandler(key, value, db, context)
			}
		}
	}
	context.SetDB(db)
	return context
}

func (s *Searcher) getContext() *qor.Context {
	return s.Context.Context.New()
}

func (s *Searcher) parseContext() *qor.Context {
	var context = s.getContext()

	if context != nil && context.Request != nil {
		// parse scopes
		scopes := strings.Split(context.Request.Form.Get("scopes"), "|")
		s.Scope(scopes...)

		// parse filters
		for key, value := range context.Request.Form {
			if matches := filterRegexp.FindStringSubmatch(key); len(matches) > 0 {
				s.Filter(matches[1], value[0])
			}
		}
	}

	s.callScopes(context)

	return context
}

func (s *Searcher) FindAll() (interface{}, error) {
	context := s.parseContext()
	result := s.Resource.NewSlice()
	err := s.Resource.CallSearcher(result, context)
	return result, err
}

func (s *Searcher) FindOne() (interface{}, error) {
	context := s.parseContext()
	result := s.Resource.NewStruct()
	err := s.Resource.CallFinder(result, nil, context)
	return result, err
}
<|endoftext|>