<commit_before>package extensions

import (
	"encoding/json"
	"fmt"
	"github.com/pawelszydlo/papa-bot"
	"net/url"
	"strings"
)

/*
ExtensionAqicn - query Aqicn.org for air quality data.

Used custom variables:
- AqicnToken- Your Aqicn token.
*/
type ExtensionAqicn struct {
	bot *papaBot.Bot
}

// Structs for Aqicn responses.
type aqiSearchResult struct {
	Status string
	Data   []aqiSearchData
}
type aqiSearchData struct {
	Uid int
}

type aqiQueryResult struct {
	Status string
	Data   aqiData
}
type aqiData struct {
	Aqi  int
	City aqiCity
	Iaqi aqiIaqi
}
type aqiCity struct {
	Name string
}
type aqiIaqi struct {
	No2  aqiValue
	O3   aqiValue
	Pm10 aqiValue
	Pm25 aqiValue
}
type aqiValue struct {
	V float64
}

// Init inits the extension.
func (ext *ExtensionAqicn) Init(bot *papaBot.Bot) error {
	// Register new command.
	bot.RegisterCommand(&papaBot.BotCommand{
		[]string{"aq"},
		false, false, false,
		"<station>", "Show air quality for <station>.",
		ext.commandAqicn})
	ext.bot = bot
	return nil
}

//qualityIndex shows how the value qualifies.
func (ext *ExtensionAqicn) qualityIndexLevel(stat string, value float64) int {
	norms := map[string][]int{
		"pm25": {15, 30, 55, 110},
		"pm10": {25, 50, 90, 180},
		"o3":   {60, 120, 180, 240},
		"no2":  {50, 100, 200, 400},
		"aqi": {50, 100, 150, 200},
	}
	for i, normValue := range norms[stat] {
		if int(value) < normValue {
			return i
		}
	}
	return 4
}

// interpretQualityIndex will put the quality index into human readable form.
func (ext *ExtensionAqicn) interpretQualityIndex(stat string, value float64) string {
	level := ext.qualityIndexLevel(stat, value)
	levels := map[int]string{
		0: ":smile:",
		1: ":slightly_smiling_face:",
		2: ":confused:",
		3: ":weary:",
		4: ":skull_and_crossbones:",
	}
	return levels[level]
}

// format is a helper function that will prepare a markdown value.
func (ext *ExtensionAqicn) format(stat string, value float64) string {
	if value == 0 { // no readout.
		return "- |"
	}
	return fmt.Sprintf("%.f %s |",value, ext.interpretQualityIndex(stat, value))
}

// queryAqicn will query aqicn.org first for stations matching "city", then for results for those stations.
func (ext *ExtensionAqicn) queryAqicn(city, transport string) string {
	token := ext.bot.GetVar("aqicnToken")
	if token == "" {
		ext.bot.Log.Errorf("Aqicn.org Token key not set! Set the 'aqicnToken' variable in the bot.")
	}

	err, _, body := ext.bot.GetPageBody(
		fmt.Sprintf(
			"https://api.waqi.info/search/?token=%s&keyword=%s",
			token, strings.Replace(url.QueryEscape(city), "+", "%20", -1),
		), nil)
	if err != nil {
		ext.bot.Log.Errorf("Error getting Aqicn data: %s", err)
		return ""
	}

	searchResult := aqiSearchResult{Status: "", Data: []aqiSearchData{}}
	// Decode JSON.
	if err := json.Unmarshal(body, &searchResult); err != nil {
		ext.bot.Log.Errorf("Error loading Aqicn.org data for %s: %s", city, err)
		return ""
	}

	// Check response.
	if len(searchResult.Data) == 0 {
		return ext.bot.Texts.SearchNoResults
	} else {
		ext.bot.Log.Infof("Found %d stations for city '%s'.", len(searchResult.Data), city)
	}

	// Gather data for each station.
	result := []string{}
	if transport == "mattermost" {
		result = append(result, "\n\n| Station | AQI | PM2.5 | PM10 | O3 | NO2 |")
		result = append(result, "| -----: | :----: | :----: | :----:| :----: | :----: | :----: |")
	}
	for _, station := range searchResult.Data {
		url := fmt.Sprintf("http://api.waqi.info/feed/@%d/?token=%s", station.Uid, token)
		ext.bot.Log.Warnf(url)
		err, _, body := ext.bot.GetPageBody(
			fmt.Sprintf("http://api.waqi.info/feed/@%d/?token=%s", station.Uid, token), nil)
		if err != nil {
			ext.bot.Log.Errorf("Error getting Aqicn data: %s", err)
			continue
		}
		queryResult := aqiQueryResult{"", aqiData{City: aqiCity{}, Iaqi: aqiIaqi{}}}
		// Decode JSON.
		if err := json.Unmarshal(body, &queryResult); err != nil {
			ext.bot.Log.Errorf("Error loading Aqicn.org data for %d: %s", station.Uid, err)
		} else {
			if transport == "mattermost" {
				line := fmt.Sprintf("| %s | ", queryResult.Data.City.Name)
				line += ext.format("aqi", float64(queryResult.Data.Aqi))
				line += ext.format("pm25", float64(queryResult.Data.Iaqi.Pm25.V))
				line += ext.format("pm10", float64(queryResult.Data.Iaqi.Pm10.V))
				line += ext.format("o3", float64(queryResult.Data.Iaqi.O3.V))
				line += ext.format("no2", float64(queryResult.Data.Iaqi.No2.V))
				result = append(result, line)
			} else {
				result = append(result, fmt.Sprintf(
					"%s - AQI: %d, PM10: %.f, PM25: %.f",
					queryResult.Data.City.Name, queryResult.Data.Aqi,
					queryResult.Data.Iaqi.Pm10.V, queryResult.Data.Iaqi.Pm25.V),
				)
			}
		}
	}
	if transport == "mattermost" {
		return strings.Join(result, "\n")
	}
	return strings.Join(result, " | ")
}

// commandMovie is a command for manually searching for movies.
func (ext *ExtensionAqicn) commandAqicn(bot *papaBot.Bot, nick, user, channel, transport, context string, priv bool, params []string) {
	if len(params) < 1 {
		return
	}
	search := strings.Join(params, " ")
	result := ext.queryAqicn(search, transport)

	bot.SendAutoMessage(priv, transport, nick, channel, result, context)
}
<commit_msg>Limit number of weather stations to 5.<commit_after>package extensions

import (
	"encoding/json"
	"fmt"
	"github.com/pawelszydlo/papa-bot"
	"net/url"
	"strings"
)

/*
ExtensionAqicn - query Aqicn.org for air quality data.

Used custom variables:
- AqicnToken- Your Aqicn token.
*/
type ExtensionAqicn struct {
	bot *papaBot.Bot
}

// Structs for Aqicn responses.
type aqiSearchResult struct {
	Status string
	Data   []aqiSearchData
}
type aqiSearchData struct {
	Uid int
}

type aqiQueryResult struct {
	Status string
	Data   aqiData
}
type aqiData struct {
	Aqi  int
	City aqiCity
	Iaqi aqiIaqi
}
type aqiCity struct {
	Name string
}
type aqiIaqi struct {
	No2  aqiValue
	O3   aqiValue
	Pm10 aqiValue
	Pm25 aqiValue
}
type aqiValue struct {
	V float64
}

// Init inits the extension.
func (ext *ExtensionAqicn) Init(bot *papaBot.Bot) error {
	// Register new command.
	bot.RegisterCommand(&papaBot.BotCommand{
		[]string{"aq"},
		false, false, false,
		"<station>", "Show air quality for <station>.",
		ext.commandAqicn})
	ext.bot = bot
	return nil
}

//qualityIndex shows how the value qualifies.
func (ext *ExtensionAqicn) qualityIndexLevel(stat string, value float64) int {
	norms := map[string][]int{
		"pm25": {15, 30, 55, 110},
		"pm10": {25, 50, 90, 180},
		"o3":   {60, 120, 180, 240},
		"no2":  {50, 100, 200, 400},
		"aqi": {50, 100, 150, 200},
	}
	for i, normValue := range norms[stat] {
		if int(value) < normValue {
			return i
		}
	}
	return 4
}

// interpretQualityIndex will put the quality index into human readable form.
func (ext *ExtensionAqicn) interpretQualityIndex(stat string, value float64) string {
	level := ext.qualityIndexLevel(stat, value)
	levels := map[int]string{
		0: ":smile:",
		1: ":slightly_smiling_face:",
		2: ":confused:",
		3: ":weary:",
		4: ":skull_and_crossbones:",
	}
	return levels[level]
}

// format is a helper function that will prepare a markdown value.
func (ext *ExtensionAqicn) format(stat string, value float64) string {
	if value == 0 { // no readout.
		return "- |"
	}
	return fmt.Sprintf("%.f %s |",value, ext.interpretQualityIndex(stat, value))
}

// queryAqicn will query aqicn.org first for stations matching "city", then for results for those stations.
func (ext *ExtensionAqicn) queryAqicn(city, transport string) string {
	token := ext.bot.GetVar("aqicnToken")
	if token == "" {
		ext.bot.Log.Errorf("Aqicn.org Token key not set! Set the 'aqicnToken' variable in the bot.")
	}

	err, _, body := ext.bot.GetPageBody(
		fmt.Sprintf(
			"https://api.waqi.info/search/?token=%s&keyword=%s",
			token, strings.Replace(url.QueryEscape(city), "+", "%20", -1),
		), nil)
	if err != nil {
		ext.bot.Log.Errorf("Error getting Aqicn data: %s", err)
		return ""
	}

	searchResult := aqiSearchResult{Status: "", Data: []aqiSearchData{}}
	// Decode JSON.
	if err := json.Unmarshal(body, &searchResult); err != nil {
		ext.bot.Log.Errorf("Error loading Aqicn.org data for %s: %s", city, err)
		return ""
	}

	// Check response.
	if len(searchResult.Data) == 0 {
		return ext.bot.Texts.SearchNoResults
	} else {
		ext.bot.Log.Infof("Found %d stations for city '%s'.", len(searchResult.Data), city)
	}
	// Limit number of stations to five.
	searchResult.Data = searchResult.Data[:5]

	// Gather data for each station.
	result := []string{}
	if transport == "mattermost" {
		result = append(result, "\n\n| Station | AQI | PM2.5 | PM10 | O3 | NO2 |")
		result = append(result, "| -----: | :----: | :----: | :----:| :----: | :----: | :----: |")
	}
	for _, station := range searchResult.Data {
		url := fmt.Sprintf("http://api.waqi.info/feed/@%d/?token=%s", station.Uid, token)
		ext.bot.Log.Warnf(url)
		err, _, body := ext.bot.GetPageBody(
			fmt.Sprintf("http://api.waqi.info/feed/@%d/?token=%s", station.Uid, token), nil)
		if err != nil {
			ext.bot.Log.Errorf("Error getting Aqicn data: %s", err)
			continue
		}
		queryResult := aqiQueryResult{"", aqiData{City: aqiCity{}, Iaqi: aqiIaqi{}}}
		// Decode JSON.
		if err := json.Unmarshal(body, &queryResult); err != nil {
			ext.bot.Log.Errorf("Error loading Aqicn.org data for %d: %s", station.Uid, err)
		} else {
			if transport == "mattermost" {
				line := fmt.Sprintf("| %s | ", queryResult.Data.City.Name)
				line += ext.format("aqi", float64(queryResult.Data.Aqi))
				line += ext.format("pm25", float64(queryResult.Data.Iaqi.Pm25.V))
				line += ext.format("pm10", float64(queryResult.Data.Iaqi.Pm10.V))
				line += ext.format("o3", float64(queryResult.Data.Iaqi.O3.V))
				line += ext.format("no2", float64(queryResult.Data.Iaqi.No2.V))
				result = append(result, line)
			} else {
				result = append(result, fmt.Sprintf(
					"%s - AQI: %d, PM10: %.f, PM25: %.f",
					queryResult.Data.City.Name, queryResult.Data.Aqi,
					queryResult.Data.Iaqi.Pm10.V, queryResult.Data.Iaqi.Pm25.V),
				)
			}
		}
	}
	if transport == "mattermost" {
		return strings.Join(result, "\n")
	}
	return strings.Join(result, " | ")
}

// commandMovie is a command for manually searching for movies.
func (ext *ExtensionAqicn) commandAqicn(bot *papaBot.Bot, nick, user, channel, transport, context string, priv bool, params []string) {
	if len(params) < 1 {
		return
	}
	search := strings.Join(params, " ")
	result := ext.queryAqicn(search, transport)

	bot.SendAutoMessage(priv, transport, nick, channel, result, context)
}
<|endoftext|>


<commit_before>package heap

import "GoVM/chapter3-cf/classfile"

type InterfaceMethodRef struct {
	MemberRef
	method *Method
}

func newInterfaceMethodRef(cp *ConstantPool, interfaceMethodRef *chapter3_cf.ConstantInterfaceMethodrefInfo) *InterfaceMethodRef {
	ref := &InterfaceMethodRef{}
	ref.cp = cp
	ref.copyMemberRefInfo(&interfaceMethodRef.ConstantMemberrefInfo)
	return ref
}<commit_msg>解析接口方法引用<commit_after>package heap

import "GoVM/chapter3-cf/classfile"

type InterfaceMethodRef struct {
	MemberRef
	method *Method
}

func newInterfaceMethodRef(cp *ConstantPool, interfaceMethodRef *chapter3_cf.ConstantInterfaceMethodrefInfo) *InterfaceMethodRef {
	ref := &InterfaceMethodRef{}
	ref.cp = cp
	ref.copyMemberRefInfo(&interfaceMethodRef.ConstantMemberrefInfo)
	return ref
}

func (self *InterfaceMethodRef) ResolvedInterfaceMethod() *Method {
	if self.method == nil {
		self.resolveInterfaceMethodRef()
	}
	return self.method
}

func (self *InterfaceMethodRef) resolveInterfaceMethodRef() {
	d := self.cp.class
	c := self.ResolvedClass()
	if !c.IsInterface() {
		panic("java.lang.IncompatibleClassChangeError")
	}

	method := lookupInterfaceMethod(c, self.name, self.descriptor)
	if method == nil {
		panic("java.lang.NoSuchMethodError")
	}
	if !method.isAccessibleTo(d) {
		panic("java.lang.IllegalAccessError")
	}

	self.method = method
}

func lookupInterfaceMethod(iface *Class, name, descriptor string) *Method {
	for _, method := range iface.methods {
		if method.name == name && method.descriptor == descriptor {
			return method
		}
	}
	return lookupMethodInInterface(iface.interfaces, name, descriptor)
}<|endoftext|>


<commit_before>// Copyright (c) 2016 Niklas Wolber
// This file is licensed under the MIT license.
// See the LICENSE file for more information.

package job

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"time"

	"golang.org/x/crypto/ssh"
)

type storeElement struct {
	ref      int
	client   *sshClient
	lastUsed time.Time
}

type sshClientStore struct {
	clients map[string]*storeElement
	m       sync.Mutex
}

var (
	errUnknownUser = errors.New("keyboard interactive: unknown user")
)

var (
	store *sshClientStore
)

// InitializeSSHClientStore initialies the global SSH connection store and
// sets the time-to-live for unused connections.
func InitializeSSHClientStore(ttl time.Duration) {
	store = &sshClientStore{
		clients: make(map[string]*storeElement),
	}

	// This go routine runs for the lifetime of the program.
	go func() {
		for {
			watchTime := time.Duration(float64(ttl.Nanoseconds()) * 0.1)
			<-time.Tick(watchTime)

			func() {
				store.m.Lock()
				defer store.m.Unlock()

				for key, elem := range store.clients {
					if diff := time.Now().Sub(elem.lastUsed); elem.ref <= 0 && diff > ttl {
						log.Println("connection to", key, "unused for", diff, "closing")
						elem.client.c.Close()
						delete(store.clients, key)
					}
				}
			}()
		}
	}()
}

func newSSHClient(ctx context.Context, addr, user, keyFile, password string, keyboardInteractive map[string]string) (*sshClient, error) {
	key := fmt.Sprintf("%s@%s", user, addr)

	store.m.Lock()
	defer store.m.Unlock()

	elem, ok := store.clients[key]

	if !ok {
		client, err := createClient(addr, user, keyFile, password, keyboardInteractive)
		if err != nil {
			return nil, err
		}

		go func(client *sshClient) {
			err := client.c.Wait()
			log.Println("connection closed, removing from store:", err)

			store.m.Lock()
			defer store.m.Unlock()
			if _, ok := store.clients[key]; ok {
				delete(store.clients, key)
			}
		}(client)

		elem = &storeElement{
			client: client,
		}
		store.clients[key] = elem
	} else {
		log.Println("reusing existing connection")
	}

	elem.ref++
	elem.lastUsed = time.Now()

	go func(ctx context.Context, client *sshClient) {
		<-ctx.Done()
		store.m.Lock()
		defer store.m.Unlock()

		store.clients[key].ref--
	}(ctx, elem.client)

	return elem.client, nil
}

type sshClient struct {
	c *ssh.Client
}

func createClient(addr, user, keyFile, password string, keyboardInteractive map[string]string) (*sshClient, error) {
	config := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{},
	}

	if keyFile != "" {
		s, _, err := readPrivateKeyFile(keyFile, nil)
		if err != nil {
			err = fmt.Errorf("Unable to read private key %s", err)
			log.Println(err)
			return nil, err
		}

		signer, err := ssh.NewSignerFromSigner(s)
		if err != nil {
			err = fmt.Errorf("Unable to turn signer into signer %s", err)
			log.Println(err)
			return nil, err
		}
		config.Auth = append(config.Auth, ssh.PublicKeys(signer))
	}

	if password != "" {
		config.Auth = append(config.Auth, ssh.Password(password))
	}

	if keyboardInteractive != nil && len(keyboardInteractive) > 0 {
		config.Auth = append(config.Auth, ssh.KeyboardInteractive(keyboardInteractiveChallenge(user, keyboardInteractive)))
	}

	log.Println("no existing connection, connecting to", addr)
	client, err := ssh.Dial("tcp", addr, config)
	if err != nil {
		return nil, err
	}

	log.Println("connected to", addr)
	return &sshClient{
		c: client,
	}, nil
}

func keyboardInteractiveChallenge(user string, keyboardInteractive map[string]string) ssh.KeyboardInteractiveChallenge {
	return func(challengeUser, instruction string, questions []string, echos []bool) ([]string, error) {
		if len(questions) == 0 {
			return nil, nil
		}

		var answers []string
		for _, question := range questions {
			if answer, ok := keyboardInteractive[question]; ok {
				answers = append(answers, answer)
			}
		}

		return answers, nil
	}
}

func (s *sshClient) executeCommand(ctx context.Context, command string, stdout, stderr io.Writer) error {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	select {
	case <-ctx.Done():
		l.Printf("won't execute %q because context is done", command)
		return nil
	default:
	}

	session, err := s.c.NewSession()
	if err != nil {
		l.Println("failed to create session:", err)
	}
	defer session.Close()

	if stdout != nil {
		session.Stdout = stdout
	}

	if stderr != nil {
		session.Stderr = stderr
	}

	l.Printf("executing %q", command)
	if err := session.Start(command); err != nil {
		l.Printf("failed to start: %q, %s", command, err)
	}

	done := make(chan error)
	go func() {
		done <- session.Wait()
	}()

	select {
	case <-ctx.Done():
		l.Println("closing session, context done")
		return nil
	case err, _ := <-done:
		if err != nil {
			l.Printf("executing %q failed: %s", command, err)
			return err
		}
	}

	l.Printf("%q executed successfully", command)
	return nil
}

func (s *sshClient) forwardRemote(ctx context.Context, remoteAddr, localAddr string) {
	forwardRemote(ctx, s.c, remoteAddr, localAddr)
}

func (s *sshClient) forwardTunnel(ctx context.Context, remoteAddr, localAddr string) {
	forwardLocal(ctx, s.c, remoteAddr, localAddr)
}
<commit_msg>Fix timer leak<commit_after>// Copyright (c) 2016 Niklas Wolber
// This file is licensed under the MIT license.
// See the LICENSE file for more information.

package job

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
	"time"

	"golang.org/x/crypto/ssh"
)

type storeElement struct {
	ref      int
	client   *sshClient
	lastUsed time.Time
}

type sshClientStore struct {
	clients map[string]*storeElement
	m       sync.Mutex
}

var (
	errUnknownUser = errors.New("keyboard interactive: unknown user")
)

var (
	store *sshClientStore
)

// InitializeSSHClientStore initialies the global SSH connection store and
// sets the time-to-live for unused connections.
func InitializeSSHClientStore(ttl time.Duration) {
	store = &sshClientStore{
		clients: make(map[string]*storeElement),
	}

	// This go routine runs for the lifetime of the program.
	go func() {
		for {
			watchTime := time.Duration(float64(ttl.Nanoseconds()) * 0.1)
			<-time.After(watchTime)

			func() {
				store.m.Lock()
				defer store.m.Unlock()

				for key, elem := range store.clients {
					if diff := time.Now().Sub(elem.lastUsed); elem.ref <= 0 && diff > ttl {
						log.Println("connection to", key, "unused for", diff, "closing")
						elem.client.c.Close()
						delete(store.clients, key)
					}
				}
			}()
		}
	}()
}

func newSSHClient(ctx context.Context, addr, user, keyFile, password string, keyboardInteractive map[string]string) (*sshClient, error) {
	key := fmt.Sprintf("%s@%s", user, addr)

	store.m.Lock()
	defer store.m.Unlock()

	elem, ok := store.clients[key]

	if !ok {
		client, err := createClient(addr, user, keyFile, password, keyboardInteractive)
		if err != nil {
			return nil, err
		}

		go func(client *sshClient) {
			err := client.c.Wait()
			log.Println("connection closed, removing from store:", err)

			store.m.Lock()
			defer store.m.Unlock()
			if _, ok := store.clients[key]; ok {
				delete(store.clients, key)
			}
		}(client)

		elem = &storeElement{
			client: client,
		}
		store.clients[key] = elem
	} else {
		log.Println("reusing existing connection")
	}

	elem.ref++
	elem.lastUsed = time.Now()

	go func(ctx context.Context, client *sshClient) {
		<-ctx.Done()
		store.m.Lock()
		defer store.m.Unlock()

		store.clients[key].ref--
	}(ctx, elem.client)

	return elem.client, nil
}

type sshClient struct {
	c *ssh.Client
}

func createClient(addr, user, keyFile, password string, keyboardInteractive map[string]string) (*sshClient, error) {
	config := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{},
	}

	if keyFile != "" {
		s, _, err := readPrivateKeyFile(keyFile, nil)
		if err != nil {
			err = fmt.Errorf("Unable to read private key %s", err)
			log.Println(err)
			return nil, err
		}

		signer, err := ssh.NewSignerFromSigner(s)
		if err != nil {
			err = fmt.Errorf("Unable to turn signer into signer %s", err)
			log.Println(err)
			return nil, err
		}
		config.Auth = append(config.Auth, ssh.PublicKeys(signer))
	}

	if password != "" {
		config.Auth = append(config.Auth, ssh.Password(password))
	}

	if keyboardInteractive != nil && len(keyboardInteractive) > 0 {
		config.Auth = append(config.Auth, ssh.KeyboardInteractive(keyboardInteractiveChallenge(user, keyboardInteractive)))
	}

	log.Println("no existing connection, connecting to", addr)
	client, err := ssh.Dial("tcp", addr, config)
	if err != nil {
		return nil, err
	}

	log.Println("connected to", addr)
	return &sshClient{
		c: client,
	}, nil
}

func keyboardInteractiveChallenge(user string, keyboardInteractive map[string]string) ssh.KeyboardInteractiveChallenge {
	return func(challengeUser, instruction string, questions []string, echos []bool) ([]string, error) {
		if len(questions) == 0 {
			return nil, nil
		}

		var answers []string
		for _, question := range questions {
			if answer, ok := keyboardInteractive[question]; ok {
				answers = append(answers, answer)
			}
		}

		return answers, nil
	}
}

func (s *sshClient) executeCommand(ctx context.Context, command string, stdout, stderr io.Writer) error {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	select {
	case <-ctx.Done():
		l.Printf("won't execute %q because context is done", command)
		return nil
	default:
	}

	session, err := s.c.NewSession()
	if err != nil {
		l.Println("failed to create session:", err)
	}
	defer session.Close()

	if stdout != nil {
		session.Stdout = stdout
	}

	if stderr != nil {
		session.Stderr = stderr
	}

	l.Printf("executing %q", command)
	if err := session.Start(command); err != nil {
		l.Printf("failed to start: %q, %s", command, err)
	}

	done := make(chan error)
	go func() {
		done <- session.Wait()
	}()

	select {
	case <-ctx.Done():
		l.Println("closing session, context done")
		return nil
	case err, _ := <-done:
		if err != nil {
			l.Printf("executing %q failed: %s", command, err)
			return err
		}
	}

	l.Printf("%q executed successfully", command)
	return nil
}

func (s *sshClient) forwardRemote(ctx context.Context, remoteAddr, localAddr string) {
	forwardRemote(ctx, s.c, remoteAddr, localAddr)
}

func (s *sshClient) forwardTunnel(ctx context.Context, remoteAddr, localAddr string) {
	forwardLocal(ctx, s.c, remoteAddr, localAddr)
}
<|endoftext|>


<commit_before>package mackerelplugin

import (
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/mackerelio/golib/pluginutil"
)

// Metric units
const (
	UnitFloat          = "float"
	UnitInteger        = "integer"
	UnitPercentage     = "percentage"
	UnitBytes          = "bytes"
	UnitBytesPerSecond = "bytes/sec"
	UnitIOPS           = "iops"
)

// Metrics represents definition of a metric
type Metrics struct {
	Name    string  `json:"name"`
	Label   string  `json:"label"`
	Diff    bool    `json:"-"`
	Stacked bool    `json:"stacked"`
	Scale   float64 `json:"-"`
}

// Graphs represents definition of a graph
type Graphs struct {
	Label   string    `json:"label"`
	Unit    string    `json:"unit"`
	Metrics []Metrics `json:"metrics"`
}

// Plugin is old interface of mackerel-plugin
type Plugin interface {
	FetchMetrics() (map[string]float64, error)
	GraphDefinition() map[string]Graphs
}

// PluginWithPrefix is recommended interface
type PluginWithPrefix interface {
	Plugin
	MetricKeyPrefix() string
}

// MackerelPlugin is for mackerel-agent-plugins
type MackerelPlugin struct {
	Plugin
	Tempfile string
	diff     *bool
	writer   io.Writer
}

// NewMackerelPlugin returns new MackrelPlugin
func NewMackerelPlugin(plugin Plugin) *MackerelPlugin {
	return &MackerelPlugin{Plugin: plugin}
}

func (mp *MackerelPlugin) getWriter() io.Writer {
	if mp.writer == nil {
		mp.writer = os.Stdout
	}
	return mp.writer
}

func (mp *MackerelPlugin) hasDiff() bool {
	if mp.diff == nil {
		diff := false
		mp.diff = &diff
	DiffCheck:
		for _, graph := range mp.GraphDefinition() {
			for _, metric := range graph.Metrics {
				if metric.Diff {
					*mp.diff = true
					break DiffCheck
				}
			}
		}
	}
	return *mp.diff
}

func (mp *MackerelPlugin) printValue(w io.Writer, key string, value float64, now time.Time) {
	if math.IsNaN(value) || math.IsInf(value, 0) {
		log.Printf("Invalid value: key = %s, value = %f\n", key, value)
		return
	}

	if value == float64(int(value)) {
		fmt.Fprintf(w, "%s\t%d\t%d\n", key, int(value), now.Unix())
	} else {
		fmt.Fprintf(w, "%s\t%f\t%d\n", key, value, now.Unix())
	}
}

var errStateRecentlyUpdated = errors.New("state was recently updated")

const oldEnoughDuration = time.Second

func (mp *MackerelPlugin) fetchLastValues(now time.Time) (map[string]float64, time.Time, error) {
	if !mp.hasDiff() {
		return nil, time.Time{}, nil
	}

	f, err := os.Open(mp.tempfilename())
	if err != nil {
		if os.IsNotExist(err) {
			return nil, time.Time{}, nil
		}
		return nil, time.Time{}, err
	}
	defer f.Close()

	stat := make(map[string]float64)
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return stat, time.Time{}, err
	}
	lastTime := time.Unix(int64(stat["_lastTime"]), 0)
	if now.Sub(lastTime) < oldEnoughDuration {
		return stat, time.Time{}, errStateRecentlyUpdated
	}
	return stat, lastTime, nil
}

func (mp *MackerelPlugin) saveValues(values map[string]float64, now time.Time) error {
	if !mp.hasDiff() {
		return nil
	}
	f, err := os.Create(mp.tempfilename())
	if err != nil {
		return err
	}
	defer f.Close()

	// Since Go 1.15 strconv.ParseFloat returns +Inf if it couldn't parse a string.
	// But JSON is not accept invalid numbers, such as +Inf, -Inf or NaN.
	// We perhaps have some plugins that is affected above change,
	// so saveState should clear invalid numbers in the values before saving it.
	for k, v := range values {
		if math.IsInf(v, 0) || math.IsNaN(v) {
			delete(values, k)
		}
	}

	values["_lastTime"] = float64(now.Unix())
	encoder := json.NewEncoder(f)
	err = encoder.Encode(values)
	if err != nil {
		return err
	}

	return nil
}

func (mp *MackerelPlugin) calcDiff(value float64, now time.Time, lastValue float64, lastTime time.Time) (float64, error) {
	diffTime := now.Unix() - lastTime.Unix()
	if diffTime > 600 {
		return 0, errors.New("too long duration")
	}

	diff := (value - lastValue) * 60 / float64(diffTime)
	if diff < 0 {
		return 0, errors.New("counter seems to be reset")
	}
	return diff, nil
}

func (mp *MackerelPlugin) tempfilename() string {
	if mp.Tempfile == "" {
		mp.Tempfile = mp.generateTempfilePath(os.Args)
	}
	return mp.Tempfile
}

var tempfileSanitizeReg = regexp.MustCompile(`[^-_.A-Za-z0-9]`)

// SetTempfileByBasename sets Tempfile under proper directory with specified basename.
func (mp *MackerelPlugin) SetTempfileByBasename(base string) {
	mp.Tempfile = filepath.Join(pluginutil.PluginWorkDir(), base)
}

func (mp *MackerelPlugin) generateTempfilePath(args []string) string {
	commandPath := args[0]
	var prefix string
	if p, ok := mp.Plugin.(PluginWithPrefix); ok {
		prefix = p.MetricKeyPrefix()
	} else {
		name := filepath.Base(commandPath)
		prefix = strings.TrimPrefix(tempfileSanitizeReg.ReplaceAllString(name, "_"), "mackerel-plugin-")
	}
	filename := fmt.Sprintf(
		"mackerel-plugin-%s-%x",
		prefix,
		// When command-line options are different, mostly different metrics.
		// e.g. `-host` and `-port` options for mackerel-plugin-mysql
		sha1.Sum([]byte(strings.Join(args[1:], " "))),
	)
	return filepath.Join(pluginutil.PluginWorkDir(), filename)
}

// OutputValues output the metrics
func (mp *MackerelPlugin) OutputValues() {
	now := time.Now()
	stat, err := mp.FetchMetrics()
	if err != nil {
		log.Fatalln("OutputValues: ", err)
	}

	lastStat, lastTime, err := mp.fetchLastValues(now)
	if err != nil {
		if err == errStateRecentlyUpdated {
			log.Println("OutputValues:", err)
			return
		}
		log.Println("fetchLastValues (ignore):", err)
	}

	for key, graph := range mp.GraphDefinition() {
		for _, metric := range graph.Metrics {
			if strings.ContainsAny(key+metric.Name, "*#") {
				mp.formatValuesWithWildcard(key, metric, stat, lastStat, now, lastTime)
			} else {
				mp.formatValues(key, metric, stat, lastStat, now, lastTime)
			}
		}
	}

	err = mp.saveValues(stat, now)
	if err != nil {
		log.Fatalf("saveValues: %s", err)
	}
}

func (mp *MackerelPlugin) formatValuesWithWildcard(prefix string, metric Metrics, stat map[string]float64, lastStat map[string]float64, now time.Time, lastTime time.Time) {
	regexpStr := `\A` + prefix + "." + metric.Name
	regexpStr = strings.Replace(regexpStr, ".", `\.`, -1)
	regexpStr = strings.Replace(regexpStr, "*", `[-a-zA-Z0-9_]+`, -1)
	regexpStr = strings.Replace(regexpStr, "#", `[-a-zA-Z0-9_]+`, -1)
	re, err := regexp.Compile(regexpStr)
	if err != nil {
		log.Fatalln("Failed to compile regexp: ", err)
	}
	for k := range stat {
		if re.MatchString(k) {
			metricEach := metric
			metricEach.Name = k
			mp.formatValues("", metricEach, stat, lastStat, now, lastTime)
		}
	}
}

func (mp *MackerelPlugin) formatValues(prefix string, metric Metrics, stat map[string]float64, lastStat map[string]float64, now time.Time, lastTime time.Time) {
	name := metric.Name
	value, ok := stat[name]
	if !ok {
		return
	}
	if metric.Diff {
		lastValue, ok := lastStat[name]
		if ok {
			var err error
			value, err = mp.calcDiff(value, now, lastValue, lastTime)
			if err != nil {
				log.Println("OutputValues: ", err)
			}
		} else {
			log.Printf("%s does not exist at last fetch\n", metric.Name)
			return
		}
	}

	if metric.Scale != 0 {
		value *= metric.Scale
	}

	metricNames := []string{}
	if p, ok := mp.Plugin.(PluginWithPrefix); ok {
		metricNames = append(metricNames, p.MetricKeyPrefix())
	}
	if prefix != "" {
		metricNames = append(metricNames, prefix)
	}
	metricNames = append(metricNames, metric.Name)
	mp.printValue(mp.getWriter(), strings.Join(metricNames, "."), value, now)
}

// GraphDef is graph definitions
type GraphDef struct {
	Graphs map[string]Graphs `json:"graphs"`
}

func title(s string) string {
	r := strings.NewReplacer(".", " ", "_", " ", "*", "", "#", "")
	return strings.TrimSpace(strings.Title(r.Replace(s)))
}

// OutputDefinitions outputs graph definitions
func (mp *MackerelPlugin) OutputDefinitions() {
	fmt.Fprintln(mp.getWriter(), "# mackerel-agent-plugin")
	graphs := make(map[string]Graphs)
	for key, graph := range mp.GraphDefinition() {
		g := graph
		k := key
		if p, ok := mp.Plugin.(PluginWithPrefix); ok {
			prefix := p.MetricKeyPrefix()
			if k == "" {
				k = prefix
			} else {
				k = prefix + "." + k
			}
		}
		if g.Label == "" {
			g.Label = title(k)
		}
		metrics := []Metrics{}
		for _, v := range g.Metrics {
			if v.Label == "" {
				v.Label = title(v.Name)
			}
			metrics = append(metrics, v)
		}
		g.Metrics = metrics
		graphs[k] = g
	}
	var graphdef GraphDef
	graphdef.Graphs = graphs
	b, err := json.Marshal(graphdef)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Fprintln(mp.getWriter(), string(b))
}

// Run the plugin
func (mp *MackerelPlugin) Run() {
	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		mp.OutputDefinitions()
	} else {
		mp.OutputValues()
	}
}
<commit_msg>[nit] change a comment<commit_after>package mackerelplugin

import (
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"math"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/mackerelio/golib/pluginutil"
)

// Metric units
const (
	UnitFloat          = "float"
	UnitInteger        = "integer"
	UnitPercentage     = "percentage"
	UnitBytes          = "bytes"
	UnitBytesPerSecond = "bytes/sec"
	UnitIOPS           = "iops"
)

// Metrics represents definition of a metric
type Metrics struct {
	Name    string  `json:"name"`
	Label   string  `json:"label"`
	Diff    bool    `json:"-"`
	Stacked bool    `json:"stacked"`
	Scale   float64 `json:"-"`
}

// Graphs represents definition of a graph
type Graphs struct {
	Label   string    `json:"label"`
	Unit    string    `json:"unit"`
	Metrics []Metrics `json:"metrics"`
}

// Plugin is old interface of mackerel-plugin
type Plugin interface {
	FetchMetrics() (map[string]float64, error)
	GraphDefinition() map[string]Graphs
}

// PluginWithPrefix is recommended interface
type PluginWithPrefix interface {
	Plugin
	MetricKeyPrefix() string
}

// MackerelPlugin is for mackerel-agent-plugins
type MackerelPlugin struct {
	Plugin
	Tempfile string
	diff     *bool
	writer   io.Writer
}

// NewMackerelPlugin returns new MackrelPlugin
func NewMackerelPlugin(plugin Plugin) *MackerelPlugin {
	return &MackerelPlugin{Plugin: plugin}
}

func (mp *MackerelPlugin) getWriter() io.Writer {
	if mp.writer == nil {
		mp.writer = os.Stdout
	}
	return mp.writer
}

func (mp *MackerelPlugin) hasDiff() bool {
	if mp.diff == nil {
		diff := false
		mp.diff = &diff
	DiffCheck:
		for _, graph := range mp.GraphDefinition() {
			for _, metric := range graph.Metrics {
				if metric.Diff {
					*mp.diff = true
					break DiffCheck
				}
			}
		}
	}
	return *mp.diff
}

func (mp *MackerelPlugin) printValue(w io.Writer, key string, value float64, now time.Time) {
	if math.IsNaN(value) || math.IsInf(value, 0) {
		log.Printf("Invalid value: key = %s, value = %f\n", key, value)
		return
	}

	if value == float64(int(value)) {
		fmt.Fprintf(w, "%s\t%d\t%d\n", key, int(value), now.Unix())
	} else {
		fmt.Fprintf(w, "%s\t%f\t%d\n", key, value, now.Unix())
	}
}

var errStateRecentlyUpdated = errors.New("state was recently updated")

const oldEnoughDuration = time.Second

func (mp *MackerelPlugin) fetchLastValues(now time.Time) (map[string]float64, time.Time, error) {
	if !mp.hasDiff() {
		return nil, time.Time{}, nil
	}

	f, err := os.Open(mp.tempfilename())
	if err != nil {
		if os.IsNotExist(err) {
			return nil, time.Time{}, nil
		}
		return nil, time.Time{}, err
	}
	defer f.Close()

	stat := make(map[string]float64)
	decoder := json.NewDecoder(f)
	err = decoder.Decode(&stat)
	if err != nil {
		return stat, time.Time{}, err
	}
	lastTime := time.Unix(int64(stat["_lastTime"]), 0)
	if now.Sub(lastTime) < oldEnoughDuration {
		return stat, time.Time{}, errStateRecentlyUpdated
	}
	return stat, lastTime, nil
}

func (mp *MackerelPlugin) saveValues(values map[string]float64, now time.Time) error {
	if !mp.hasDiff() {
		return nil
	}
	f, err := os.Create(mp.tempfilename())
	if err != nil {
		return err
	}
	defer f.Close()

	// Since Go 1.15 strconv.ParseFloat returns +Inf if it couldn't parse a string.
	// But JSON does not accept invalid numbers, such as +Inf, -Inf or NaN.
	// We perhaps have some plugins that is affected above change,
	// so saveState should clear invalid numbers in the values before saving it.
	for k, v := range values {
		if math.IsInf(v, 0) || math.IsNaN(v) {
			delete(values, k)
		}
	}

	values["_lastTime"] = float64(now.Unix())
	encoder := json.NewEncoder(f)
	err = encoder.Encode(values)
	if err != nil {
		return err
	}

	return nil
}

func (mp *MackerelPlugin) calcDiff(value float64, now time.Time, lastValue float64, lastTime time.Time) (float64, error) {
	diffTime := now.Unix() - lastTime.Unix()
	if diffTime > 600 {
		return 0, errors.New("too long duration")
	}

	diff := (value - lastValue) * 60 / float64(diffTime)
	if diff < 0 {
		return 0, errors.New("counter seems to be reset")
	}
	return diff, nil
}

func (mp *MackerelPlugin) tempfilename() string {
	if mp.Tempfile == "" {
		mp.Tempfile = mp.generateTempfilePath(os.Args)
	}
	return mp.Tempfile
}

var tempfileSanitizeReg = regexp.MustCompile(`[^-_.A-Za-z0-9]`)

// SetTempfileByBasename sets Tempfile under proper directory with specified basename.
func (mp *MackerelPlugin) SetTempfileByBasename(base string) {
	mp.Tempfile = filepath.Join(pluginutil.PluginWorkDir(), base)
}

func (mp *MackerelPlugin) generateTempfilePath(args []string) string {
	commandPath := args[0]
	var prefix string
	if p, ok := mp.Plugin.(PluginWithPrefix); ok {
		prefix = p.MetricKeyPrefix()
	} else {
		name := filepath.Base(commandPath)
		prefix = strings.TrimPrefix(tempfileSanitizeReg.ReplaceAllString(name, "_"), "mackerel-plugin-")
	}
	filename := fmt.Sprintf(
		"mackerel-plugin-%s-%x",
		prefix,
		// When command-line options are different, mostly different metrics.
		// e.g. `-host` and `-port` options for mackerel-plugin-mysql
		sha1.Sum([]byte(strings.Join(args[1:], " "))),
	)
	return filepath.Join(pluginutil.PluginWorkDir(), filename)
}

// OutputValues output the metrics
func (mp *MackerelPlugin) OutputValues() {
	now := time.Now()
	stat, err := mp.FetchMetrics()
	if err != nil {
		log.Fatalln("OutputValues: ", err)
	}

	lastStat, lastTime, err := mp.fetchLastValues(now)
	if err != nil {
		if err == errStateRecentlyUpdated {
			log.Println("OutputValues:", err)
			return
		}
		log.Println("fetchLastValues (ignore):", err)
	}

	for key, graph := range mp.GraphDefinition() {
		for _, metric := range graph.Metrics {
			if strings.ContainsAny(key+metric.Name, "*#") {
				mp.formatValuesWithWildcard(key, metric, stat, lastStat, now, lastTime)
			} else {
				mp.formatValues(key, metric, stat, lastStat, now, lastTime)
			}
		}
	}

	err = mp.saveValues(stat, now)
	if err != nil {
		log.Fatalf("saveValues: %s", err)
	}
}

func (mp *MackerelPlugin) formatValuesWithWildcard(prefix string, metric Metrics, stat map[string]float64, lastStat map[string]float64, now time.Time, lastTime time.Time) {
	regexpStr := `\A` + prefix + "." + metric.Name
	regexpStr = strings.Replace(regexpStr, ".", `\.`, -1)
	regexpStr = strings.Replace(regexpStr, "*", `[-a-zA-Z0-9_]+`, -1)
	regexpStr = strings.Replace(regexpStr, "#", `[-a-zA-Z0-9_]+`, -1)
	re, err := regexp.Compile(regexpStr)
	if err != nil {
		log.Fatalln("Failed to compile regexp: ", err)
	}
	for k := range stat {
		if re.MatchString(k) {
			metricEach := metric
			metricEach.Name = k
			mp.formatValues("", metricEach, stat, lastStat, now, lastTime)
		}
	}
}

func (mp *MackerelPlugin) formatValues(prefix string, metric Metrics, stat map[string]float64, lastStat map[string]float64, now time.Time, lastTime time.Time) {
	name := metric.Name
	value, ok := stat[name]
	if !ok {
		return
	}
	if metric.Diff {
		lastValue, ok := lastStat[name]
		if ok {
			var err error
			value, err = mp.calcDiff(value, now, lastValue, lastTime)
			if err != nil {
				log.Println("OutputValues: ", err)
			}
		} else {
			log.Printf("%s does not exist at last fetch\n", metric.Name)
			return
		}
	}

	if metric.Scale != 0 {
		value *= metric.Scale
	}

	metricNames := []string{}
	if p, ok := mp.Plugin.(PluginWithPrefix); ok {
		metricNames = append(metricNames, p.MetricKeyPrefix())
	}
	if prefix != "" {
		metricNames = append(metricNames, prefix)
	}
	metricNames = append(metricNames, metric.Name)
	mp.printValue(mp.getWriter(), strings.Join(metricNames, "."), value, now)
}

// GraphDef is graph definitions
type GraphDef struct {
	Graphs map[string]Graphs `json:"graphs"`
}

func title(s string) string {
	r := strings.NewReplacer(".", " ", "_", " ", "*", "", "#", "")
	return strings.TrimSpace(strings.Title(r.Replace(s)))
}

// OutputDefinitions outputs graph definitions
func (mp *MackerelPlugin) OutputDefinitions() {
	fmt.Fprintln(mp.getWriter(), "# mackerel-agent-plugin")
	graphs := make(map[string]Graphs)
	for key, graph := range mp.GraphDefinition() {
		g := graph
		k := key
		if p, ok := mp.Plugin.(PluginWithPrefix); ok {
			prefix := p.MetricKeyPrefix()
			if k == "" {
				k = prefix
			} else {
				k = prefix + "." + k
			}
		}
		if g.Label == "" {
			g.Label = title(k)
		}
		metrics := []Metrics{}
		for _, v := range g.Metrics {
			if v.Label == "" {
				v.Label = title(v.Name)
			}
			metrics = append(metrics, v)
		}
		g.Metrics = metrics
		graphs[k] = g
	}
	var graphdef GraphDef
	graphdef.Graphs = graphs
	b, err := json.Marshal(graphdef)
	if err != nil {
		log.Fatalln("OutputDefinitions: ", err)
	}
	fmt.Fprintln(mp.getWriter(), string(b))
}

// Run the plugin
func (mp *MackerelPlugin) Run() {
	if os.Getenv("MACKEREL_AGENT_PLUGIN_META") != "" {
		mp.OutputDefinitions()
	} else {
		mp.OutputValues()
	}
}
<|endoftext|>


<commit_before>package mailbox

import (
	"runtime"
	"sync/atomic"

	"github.com/AsynkronIT/protoactor-go/internal/queue/mpsc"
	"github.com/AsynkronIT/protoactor-go/log"
)

type Statistics interface {
	MailboxStarted()
	MessagePosted(message interface{})
	MessageReceived(message interface{})
	MailboxEmpty()
}

// MessageInvoker is the interface used by a mailbox to forward messages for processing
type MessageInvoker interface {
	InvokeSystemMessage(interface{})
	InvokeUserMessage(interface{})
	EscalateFailure(reason interface{}, message interface{})
}

// The Inbound interface is used to enqueue messages to the mailbox
type Inbound interface {
	PostUserMessage(message interface{})
	PostSystemMessage(message interface{})
	Start()
}

// Producer is a function which creates a new mailbox
type Producer func(invoker MessageInvoker, dispatcher Dispatcher) Inbound

const (
	idle int32 = iota
	running
)

const (
	hasNoMessages int32 = iota
	hasMoreMessages
)

type defaultMailbox struct {
	userMailbox     queue
	systemMailbox   *mpsc.Queue
	schedulerStatus int32
	hasMoreMessages int32
	invoker         MessageInvoker
	dispatcher      Dispatcher
	suspended       bool
	mailboxStats    []Statistics
}

func (m *defaultMailbox) PostUserMessage(message interface{}) {
	for _, ms := range m.mailboxStats {
		ms.MessagePosted(message)
	}
	m.userMailbox.Push(message)
	m.schedule()
}

func (m *defaultMailbox) PostSystemMessage(message interface{}) {
	m.systemMailbox.Push(message)
	m.schedule()
}

func (m *defaultMailbox) schedule() {
	atomic.StoreInt32(&m.hasMoreMessages, hasMoreMessages) //we have more messages to process
	if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
		m.dispatcher.Schedule(m.processMessages)
	}
}

func (m *defaultMailbox) processMessages() {
	//we are about to start processing messages, we can safely reset the message flag of the mailbox
	atomic.StoreInt32(&m.hasMoreMessages, hasNoMessages)

process:
	m.run()

	// set mailbox to idle
	atomic.StoreInt32(&m.schedulerStatus, idle)

	// check if there are still messages to process (sent after the message loop ended)
	if atomic.SwapInt32(&m.hasMoreMessages, hasNoMessages) == hasMoreMessages {
		// try setting the mailbox back to running
		if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
			goto process
		}
	}

	for _, ms := range m.mailboxStats {
		ms.MailboxEmpty()
	}
}

func (m *defaultMailbox) run() {
	var msg interface{}

	defer func() {
		if r := recover(); r != nil {
			//force the has more messages to be true.
			//if there was a lot of messages on the queue, and we exit here.
			//there will be messages left at the queue that are not scheduled
			atomic.SwapInt32(&m.hasMoreMessages, hasMoreMessages)
			plog.Debug("[ACTOR] Recovering", log.Object("actor", m.invoker), log.Object("reason", r), log.Stack())
			m.invoker.EscalateFailure(r, msg)
		}
	}()

	i, t := 0, m.dispatcher.Throughput()
	for {
		if i > t {
			i = 0
			runtime.Gosched()
		}

		i++

		// keep processing system messages until queue is empty
		if msg = m.systemMailbox.Pop(); msg != nil {
			switch msg.(type) {
			case *SuspendMailbox:
				m.suspended = true
			case *ResumeMailbox:
				m.suspended = false
			default:
				m.invoker.InvokeSystemMessage(msg)
			}

			continue
		}

		// didn't process a system message, so break until we are resumed
		if m.suspended {
			return
		}

		if msg = m.userMailbox.Pop(); msg != nil {
			m.invoker.InvokeUserMessage(msg)
			for _, ms := range m.mailboxStats {
				ms.MessageReceived(msg)
			}
		} else {
			return
		}
	}

}

func (m *defaultMailbox) Start() {
	for _, ms := range m.mailboxStats {
		ms.MailboxStarted()
	}
}
<commit_msg>mailbox counting messages<commit_after>package mailbox

import (
	"runtime"
	"sync/atomic"

	"github.com/AsynkronIT/protoactor-go/internal/queue/mpsc"
	"github.com/AsynkronIT/protoactor-go/log"
)

type Statistics interface {
	MailboxStarted()
	MessagePosted(message interface{})
	MessageReceived(message interface{})
	MailboxEmpty()
}

// MessageInvoker is the interface used by a mailbox to forward messages for processing
type MessageInvoker interface {
	InvokeSystemMessage(interface{})
	InvokeUserMessage(interface{})
	EscalateFailure(reason interface{}, message interface{})
}

// The Inbound interface is used to enqueue messages to the mailbox
type Inbound interface {
	PostUserMessage(message interface{})
	PostSystemMessage(message interface{})
	Start()
}

// Producer is a function which creates a new mailbox
type Producer func(invoker MessageInvoker, dispatcher Dispatcher) Inbound

const (
	idle int32 = iota
	running
)

type defaultMailbox struct {
	userMailbox     queue
	systemMailbox   *mpsc.Queue
	schedulerStatus int32
	userMessages    int32
	sysMessages     int32
	invoker         MessageInvoker
	dispatcher      Dispatcher
	suspended       bool
	mailboxStats    []Statistics
}

func (m *defaultMailbox) PostUserMessage(message interface{}) {
	for _, ms := range m.mailboxStats {
		ms.MessagePosted(message)
	}
	m.userMailbox.Push(message)
	atomic.AddInt32(&m.userMessages, 1)
	m.schedule()
}

func (m *defaultMailbox) PostSystemMessage(message interface{}) {
	m.systemMailbox.Push(message)
	atomic.AddInt32(&m.sysMessages, 1)
	m.schedule()
}

func (m *defaultMailbox) schedule() {
	if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
		m.dispatcher.Schedule(m.processMessages)
	}
}

func (m *defaultMailbox) processMessages() {

process:
	m.run()

	// set mailbox to idle
	atomic.StoreInt32(&m.schedulerStatus, idle)
	sys := atomic.LoadInt32(&m.sysMessages)
	user := atomic.LoadInt32(&m.userMessages)
	// check if there are still messages to process (sent after the message loop ended)
	if sys > 0 || (!m.suspended && user > 0) {
		// try setting the mailbox back to running
		if atomic.CompareAndSwapInt32(&m.schedulerStatus, idle, running) {
			//	fmt.Printf("looping %v %v %v\n", sys, user, m.suspended)
			goto process
		}
	}

	for _, ms := range m.mailboxStats {
		ms.MailboxEmpty()
	}
}

func (m *defaultMailbox) run() {
	var msg interface{}

	defer func() {
		if r := recover(); r != nil {
			plog.Debug("[ACTOR] Recovering", log.Object("actor", m.invoker), log.Object("reason", r), log.Stack())
			m.invoker.EscalateFailure(r, msg)
		}
	}()

	i, t := 0, m.dispatcher.Throughput()
	for {
		if i > t {
			i = 0
			runtime.Gosched()
		}

		i++

		// keep processing system messages until queue is empty
		if msg = m.systemMailbox.Pop(); msg != nil {
			switch msg.(type) {
			case *SuspendMailbox:
				m.suspended = true
			case *ResumeMailbox:
				m.suspended = false
			default:
				atomic.AddInt32(&m.sysMessages, -1)
				m.invoker.InvokeSystemMessage(msg)
			}

			continue
		}

		// didn't process a system message, so break until we are resumed
		if m.suspended {
			return
		}

		if msg = m.userMailbox.Pop(); msg != nil {
			atomic.AddInt32(&m.userMessages, -1)
			m.invoker.InvokeUserMessage(msg)
			for _, ms := range m.mailboxStats {

				ms.MessageReceived(msg)
			}
		} else {
			return
		}
	}

}

func (m *defaultMailbox) Start() {
	for _, ms := range m.mailboxStats {
		ms.MailboxStarted()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"conui"
	"core"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/nsf/termbox-go"
)

func NewSyncCommand() cli.Command {
	return cli.Command{
		Name:  "sync",
		Usage: "Synchronize files to devices",
		Action: func(c *cli.Context) {
			err := checkEnvVariables(c)
			if err != nil {
				panic(fatal{fmt.Sprintf("Could not set environment variables: %s", err)})
			}
			if !c.GlobalBool("no-file-log") {
				lp := cleanPath(c.GlobalString("log"))
				var err error
				GDS_LOG_FD, err = os.Create(lp)
				if err != nil {
					panic(fatal{fmt.Sprintf("Could not create log file: %s", err)})
				}
				log.Out = GDS_LOG_FD
			}
			lvl, err := logrus.ParseLevel(c.GlobalString("log-level"))
			if err != nil {
				panic(fatalShowHelp{fmt.Sprintf("Error parsing log level: %s", err)})
			}
			log.Level = lvl
			syncStart(c)
		},
	}
}

// loadInitialState prepares the applicaton for usage
func loadInitialState(c *cli.Context) *core.Context {
	cPath, err := getConfigFile(c.GlobalString("config"))
	if err != nil {
		panic(fatal{err})
	}
	log.WithFields(logrus.Fields{
		"path": cPath,
	}).Info("Using configuration file")

	c2, err := core.ContextFromPath(cPath)
	if err != nil {
		panic(fatal{fmt.Sprintf("Error loading config: %s", err.Error())})
	}

	c2.Files, err = core.NewFileList(c2)
	if err != nil {
		panic(fatal{fmt.Sprintf("Error retrieving FileList %s", err.Error())})
	}

	c2.Catalog, err = core.NewCatalog(c2)
	if err != nil {
		// Not wrapped in fatal here because NewCatalog returns custom error types
		panic(err)
	}

	return c2
}

func dumpContextToFile(c *cli.Context, c2 *core.Context) {
	cf, err := getContextFile(c.GlobalString("context"))
	if err != nil {
		panic(fatal{fmt.Sprintf("Could not create context JSON output file: %s", err.Error())})
	}
	j, err := json.Marshal(c2)
	if err == nil {
		err = ioutil.WriteFile(cf, j, 0644)
	}
	if err != nil {
		panic(fatal{fmt.Sprintf("Could not marshal JSON to file: %s", err.Error())})
	}
}

// BuildConsole creates the UI widgets First is the main progress guage for the overall progress Widgets are then created for
// each of the devices, but are hidden initially.
func BuildConsole(c *core.Context) {
	visible := c.OutputStreamNum
	for x, y := range c.Devices {
		conui.Body.DevicePanels = append(conui.Body.DevicePanels, conui.NewDevicePanel(y.Name, y.SizeTotal))
		if visible > 0 {
			log.Debugln("Making device", x, "visible")
			conui.Body.DevicePanels[x].SetVisible(true)
			if x == 0 {
				conui.Body.DevicePanels[x].SetSelected(true)
			}
			visible--
		}
	}
	conui.Body.ProgressPanel = conui.NewProgressGauge(c.Catalog.TotalSize())
	conui.Body.ProgressPanel.SetVisible(true)
	conui.Layout()
}

func eventListener(c *core.Context) {
	defer cleanupAtExit()
	for {
		select {
		case e := <-conui.Events:
			if e.Type == conui.EventKey && e.Ch == 'j' {
				conui.Body.SelectNext()
			}
			if e.Type == conui.EventKey && e.Ch == 'k' {
				conui.Body.SelectPrevious()
			}
			if e.Type == conui.EventKey && e.Ch == 'd' {
				p := conui.Body.Selected()
				p.SetVisible(false)
			}
			if e.Type == conui.EventKey && e.Ch == 's' {
				p := conui.Body.Selected()
				p.SetVisible(true)
			}
			if e.Type == conui.EventKey && e.Key == conui.KeyEnter {
				p := conui.Body.Selected().Prompt()
				if p != nil {
					p.Action()
				}
			}
			if e.Type == conui.EventKey && e.Ch == 'q' {
				conui.Close()
				c.Exit = true
				break
			}
			if e.Type == conui.EventResize {
				conui.Layout()
				go func() { conui.Redraw <- true }()
			}
		case <-conui.Redraw:
			conui.Render()
		}
	}
}

// deviceMountHandler checks to see if the device is mounted and writable. Meant to be run as a goroutine.
func deviceMountHandler(c *core.Context, deviceIndex int) {
	// Listen on the channel for a mount request
	ns := time.Now()
	log.Debugf("Waiting for receive on SyncDeviceMount[%d]", deviceIndex)
	<-c.SyncDeviceMount[deviceIndex]
	log.Debugf("Receive from SyncDeviceMount[%d] after wait of %s", deviceIndex, time.Since(ns))

	d := &c.Devices[deviceIndex]
	wg := conui.Body.DevicePanelByIndex(deviceIndex)
	wg.SetVisible(true)

	deviceIsReady := false
	checkDevice := func(p *conui.PromptAction) error {
		// The actual checking
		err := ensureDeviceIsReady(*d)
		if err != nil {
			log.Errorf("checkDevice error: %s", err)
			switch err.(type) {
			case deviceTestPermissionDeniedError:
				p.Message = "Device is mounted but not writable... " +
					"Please fix write permissions then press Enter to continue."
			case deviceNotFoundByUUIDError:
				p.Message = "Please mount device and press Enter to continue..."
			}
			return err
		}
		deviceIsReady = true
		if deviceIndex == 0 {
			wg.SetSelected(true)
		}
		return err
	}

	// The prompt that will be displayed in the device panel
	var pAction func()
	prompt := &conui.PromptAction{}
	// Allow the user to press enter on the device panel to force a device check
	pAction = func() {
		// With the device selected in the panel, the user has pressed the enter key.
		log.Printf("Action for panel %q!", wg.Border.Label)
		checkDevice(prompt)
	}
	prompt.Action = pAction

	// Finally, set the prompt for the device in the panel
	wg.SetPrompt(prompt)

	// Check device automatically periodically
	for {
		if deviceIsReady {
			break
		}
		// Rate limit
		err := checkDevice(prompt)
		if err != nil {
			time.Sleep(time.Second * 15)
			continue
		}
		break
	}

	// The prompt is not needed anymore
	wg.SetPrompt(nil)
	c.SyncDeviceMount[deviceIndex] <- true
}

func update(c *core.Context) {
	// Main progress panel updater
	go func() {
		for {
			p := <-c.SyncProgress.Report
			prg := conui.Body.ProgressPanel
			prg.SizeWritn = p.SizeWritn
			prg.BytesPerSecond = p.BytesPerSecond
			if c.Exit {
				break
			}
		}
	}()
	// Device panel updaters
	for x := 0; x < len(c.Devices); x++ {
		go deviceMountHandler(c, x)
		c.SyncDeviceMount[x] = make(chan bool)
		go func(index int) {
			dw := conui.Body.DevicePanelByIndex(index)
			for {
				if fp, ok := <-c.SyncProgress.Device[index].Report; ok {
					dw.SizeWritn += fp.DeviceSizeWritn
					dw.BytesPerSecond = fp.DeviceBytesPerSecond
					log.WithFields(logrus.Fields{
						"fp.FileName":           fp.FileName,
						"fp.FilePath":           fp.FilePath,
						"fp.FileSize":           fp.FileSize,
						"fp.FileSizeWritn":      fp.FileSizeWritn,
						"fp.FileTotalSizeWritn": fp.FileTotalSizeWritn,
						"deviceIndex":           index,
					}).Debugln("Sync file progress")
				} else if !ok || c.Exit {
					dw.BytesPerSecondVisible = false
					break
				}
			}
			log.Debugln("DONE REPORTING index:", index)
		}(x)
	}
	go func() {
		for {
			if !termbox.IsInit || c.Exit {
				break
			}
			conui.Redraw <- true
			// Rate limit redrawing
			time.Sleep(time.Second / 3)
		}
	}()
}

func syncStart(c *cli.Context) {
	defer cleanupAtExit()
	log.WithFields(logrus.Fields{
		"version": 0.2,
		"date":    time.Now().Format(time.RFC3339),
	}).Infoln("Ghetto Device Storage")
	c2 := loadInitialState(c)

	conui.Init()
	BuildConsole(c2)
	go eventListener(c2)
	update(c2)

	errChan := make(chan error, 100)

	// Sync the things
	go func() {
		core.Sync(c2, c.GlobalBool("no-dev-context"), errChan)
		log.Info("ALL DONE -- Sync complete!")
		// c2.Exit = true
	}()

	// Give the user time to review the sync in the UI
outer:
	for {
		select {
		case err := <-errChan:
			log.Errorf("Sync error: %s", err)
		case <-time.After(time.Second):
			if c2.Exit {
				break outer
			}
		}
	}

	// Fin
	dumpContextToFile(c, c2)
}
<commit_msg>cmd/sync.go: Only show device panels if being used<commit_after>package main

import (
	"conui"
	"core"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/nsf/termbox-go"
)

func NewSyncCommand() cli.Command {
	return cli.Command{
		Name:  "sync",
		Usage: "Synchronize files to devices",
		Action: func(c *cli.Context) {
			err := checkEnvVariables(c)
			if err != nil {
				panic(fatal{fmt.Sprintf("Could not set environment variables: %s", err)})
			}
			if !c.GlobalBool("no-file-log") {
				lp := cleanPath(c.GlobalString("log"))
				var err error
				GDS_LOG_FD, err = os.Create(lp)
				if err != nil {
					panic(fatal{fmt.Sprintf("Could not create log file: %s", err)})
				}
				log.Out = GDS_LOG_FD
			}
			lvl, err := logrus.ParseLevel(c.GlobalString("log-level"))
			if err != nil {
				panic(fatalShowHelp{fmt.Sprintf("Error parsing log level: %s", err)})
			}
			log.Level = lvl
			syncStart(c)
		},
	}
}

// loadInitialState prepares the applicaton for usage
func loadInitialState(c *cli.Context) *core.Context {
	cPath, err := getConfigFile(c.GlobalString("config"))
	if err != nil {
		panic(fatal{err})
	}
	log.WithFields(logrus.Fields{
		"path": cPath,
	}).Info("Using configuration file")

	c2, err := core.ContextFromPath(cPath)
	if err != nil {
		panic(fatal{fmt.Sprintf("Error loading config: %s", err.Error())})
	}

	c2.Files, err = core.NewFileList(c2)
	if err != nil {
		panic(fatal{fmt.Sprintf("Error retrieving FileList %s", err.Error())})
	}

	c2.Catalog, err = core.NewCatalog(c2)
	if err != nil {
		// Not wrapped in fatal here because NewCatalog returns custom error types
		panic(err)
	}

	return c2
}

func dumpContextToFile(c *cli.Context, c2 *core.Context) {
	cf, err := getContextFile(c.GlobalString("context"))
	if err != nil {
		panic(fatal{fmt.Sprintf("Could not create context JSON output file: %s", err.Error())})
	}
	j, err := json.Marshal(c2)
	if err == nil {
		err = ioutil.WriteFile(cf, j, 0644)
	}
	if err != nil {
		panic(fatal{fmt.Sprintf("Could not marshal JSON to file: %s", err.Error())})
	}
}

// BuildConsole creates the UI widgets First is the main progress guage for the overall progress Widgets are then created for
// each of the devices, but are hidden initially.
func BuildConsole(c *core.Context) {
	visible := c.OutputStreamNum
	for x, y := range c.Devices {
		conui.Body.DevicePanels = append(conui.Body.DevicePanels, conui.NewDevicePanel(y.Name, y.SizeTotal))
		if visible > 0 && len(c.Catalog[y.Name]) > 0 {
			log.Debugln("Making device", x, "visible")
			conui.Body.DevicePanels[x].SetVisible(true)
			if x == 0 {
				conui.Body.DevicePanels[x].SetSelected(true)
			}
			visible--
		}
	}
	conui.Body.ProgressPanel = conui.NewProgressGauge(c.Catalog.TotalSize())
	conui.Body.ProgressPanel.SetVisible(true)
	conui.Layout()
}

func eventListener(c *core.Context) {
	defer cleanupAtExit()
	for {
		select {
		case e := <-conui.Events:
			if e.Type == conui.EventKey && e.Ch == 'j' {
				conui.Body.SelectNext()
			}
			if e.Type == conui.EventKey && e.Ch == 'k' {
				conui.Body.SelectPrevious()
			}
			if e.Type == conui.EventKey && e.Ch == 'd' {
				p := conui.Body.Selected()
				p.SetVisible(false)
			}
			if e.Type == conui.EventKey && e.Ch == 's' {
				p := conui.Body.Selected()
				p.SetVisible(true)
			}
			if e.Type == conui.EventKey && e.Key == conui.KeyEnter {
				p := conui.Body.Selected().Prompt()
				if p != nil {
					p.Action()
				}
			}
			if e.Type == conui.EventKey && e.Ch == 'q' {
				conui.Close()
				c.Exit = true
				break
			}
			if e.Type == conui.EventResize {
				conui.Layout()
				go func() { conui.Redraw <- true }()
			}
		case <-conui.Redraw:
			conui.Render()
		}
	}
}

// deviceMountHandler checks to see if the device is mounted and writable. Meant to be run as a goroutine.
func deviceMountHandler(c *core.Context, deviceIndex int) {
	// Listen on the channel for a mount request
	ns := time.Now()
	log.Debugf("Waiting for receive on SyncDeviceMount[%d]", deviceIndex)
	<-c.SyncDeviceMount[deviceIndex]
	log.Debugf("Receive from SyncDeviceMount[%d] after wait of %s", deviceIndex, time.Since(ns))

	d := &c.Devices[deviceIndex]
	wg := conui.Body.DevicePanelByIndex(deviceIndex)
	wg.SetVisible(true)

	deviceIsReady := false
	checkDevice := func(p *conui.PromptAction) error {
		// The actual checking
		err := ensureDeviceIsReady(*d)
		if err != nil {
			log.Errorf("checkDevice error: %s", err)
			switch err.(type) {
			case deviceTestPermissionDeniedError:
				p.Message = "Device is mounted but not writable... " +
					"Please fix write permissions then press Enter to continue."
			case deviceNotFoundByUUIDError:
				p.Message = "Please mount device and press Enter to continue..."
			}
			return err
		}
		deviceIsReady = true
		if deviceIndex == 0 {
			wg.SetSelected(true)
		}
		return err
	}

	// The prompt that will be displayed in the device panel
	var pAction func()
	prompt := &conui.PromptAction{}
	// Allow the user to press enter on the device panel to force a device check
	pAction = func() {
		// With the device selected in the panel, the user has pressed the enter key.
		log.Printf("Action for panel %q!", wg.Border.Label)
		checkDevice(prompt)
	}
	prompt.Action = pAction

	// Finally, set the prompt for the device in the panel
	wg.SetPrompt(prompt)

	// Check device automatically periodically
	for {
		if deviceIsReady {
			break
		}
		// Rate limit
		err := checkDevice(prompt)
		if err != nil {
			time.Sleep(time.Second * 15)
			continue
		}
		break
	}

	// The prompt is not needed anymore
	wg.SetPrompt(nil)
	c.SyncDeviceMount[deviceIndex] <- true
}

func update(c *core.Context) {
	// Main progress panel updater
	go func() {
		for {
			p := <-c.SyncProgress.Report
			prg := conui.Body.ProgressPanel
			prg.SizeWritn = p.SizeWritn
			prg.BytesPerSecond = p.BytesPerSecond
			if c.Exit {
				break
			}
		}
	}()
	// Device panel updaters
	for x := 0; x < len(c.Devices); x++ {
		go deviceMountHandler(c, x)
		c.SyncDeviceMount[x] = make(chan bool)
		go func(index int) {
			dw := conui.Body.DevicePanelByIndex(index)
			for {
				if fp, ok := <-c.SyncProgress.Device[index].Report; ok {
					dw.SizeWritn += fp.DeviceSizeWritn
					dw.BytesPerSecond = fp.DeviceBytesPerSecond
					log.WithFields(logrus.Fields{
						"fp.FileName":           fp.FileName,
						"fp.FilePath":           fp.FilePath,
						"fp.FileSize":           fp.FileSize,
						"fp.FileSizeWritn":      fp.FileSizeWritn,
						"fp.FileTotalSizeWritn": fp.FileTotalSizeWritn,
						"deviceIndex":           index,
					}).Debugln("Sync file progress")
				} else if !ok || c.Exit {
					dw.BytesPerSecondVisible = false
					break
				}
			}
			log.Debugln("DONE REPORTING index:", index)
		}(x)
	}
	go func() {
		for {
			if !termbox.IsInit || c.Exit {
				break
			}
			conui.Redraw <- true
			// Rate limit redrawing
			time.Sleep(time.Second / 3)
		}
	}()
}

func syncStart(c *cli.Context) {
	defer cleanupAtExit()
	log.WithFields(logrus.Fields{
		"version": 0.2,
		"date":    time.Now().Format(time.RFC3339),
	}).Infoln("Ghetto Device Storage")
	c2 := loadInitialState(c)

	conui.Init()
	BuildConsole(c2)
	go eventListener(c2)
	update(c2)

	errChan := make(chan error, 100)

	// Sync the things
	go func() {
		core.Sync(c2, c.GlobalBool("no-dev-context"), errChan)
		log.Info("ALL DONE -- Sync complete!")
		// c2.Exit = true
	}()

	// Give the user time to review the sync in the UI
outer:
	for {
		select {
		case err := <-errChan:
			log.Errorf("Sync error: %s", err)
		case <-time.After(time.Second):
			if c2.Exit {
				break outer
			}
		}
	}

	// Fin
	dumpContextToFile(c, c2)
}
<|endoftext|>


<commit_before>package lib

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

const chat_cmd, ask_cmd, tell_cmd, help_cmd = "/chat", "/ask", "/tell", "/help"

type QProcess struct {
	Details *Details
	lang    struct {
		questions `json:"QandA"`
	}
	next *Question
}

func NewQProcess(d *Details) *QProcess {
	return &QProcess{Details: d}
}

func (this *QProcess) Run(input <-chan telebot.Message) {
	for msg := range input {
		this.Details.User = msg.Chat
		this.saveAndFindNext(msg).andAsk()
	}
}

func (this *QProcess) loadLanguage(name string) {

	file, err := os.Open(fmt.Sprintf("./config/%s.json", name))
	if err != nil {
		log.Panic("could not load QandA language file ", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.lang)
	if err != nil {
		log.Panic("could not decode QandA ", err.Error())
	}
}

func hasSubmisson(txt string, cmds ...string) bool {
	if isCmd(txt, cmds...) {
		for i := range cmds {
			log.Println("Check if submisson", txt)
			if len(strings.SplitAfter(txt, cmds[i])) > 1 {
				return true
			}
		}
	}
	return false
}

func isCmd(txt string, cmds ...string) bool {
	log.Println("Check if cmd", txt)
	for i := range cmds {
		if strings.Contains(txt, cmds[i]) {
			return true
		}
	}
	return false
}

func (this *QProcess) saveAndFindNext(msg telebot.Message) *QProcess {
	this.next = nil

	if hasSubmisson(msg.Text, help_cmd, ask_cmd, tell_cmd) {
		this.Details.save(msg)
		langFile := strings.SplitAfter(msg.Text, "/")[1]
		log.Println("loading ...", langFile)
		this.loadLanguage(langFile)
		this.next = this.lang.questions[len(this.lang.questions)-1]
	} else if isCmd(msg.Text, help_cmd, ask_cmd, tell_cmd, chat_cmd) {
		langFile := strings.SplitAfter(msg.Text, "/")[1]
		log.Println("loading ...", langFile)
		this.loadLanguage(langFile)
		this.next = this.lang.questions[0]
	} else {
		for i := range this.lang.questions {
			for a := range this.lang.questions[i].RelatesTo.Answers {
				if this.lang.questions[i].RelatesTo.Answers[a] == msg.Text {
					//was the answer a remainder to save?
					if this.lang.questions[i].RelatesTo.Save {
						this.Details.save(msg)
					}
					this.next = this.lang.questions[i]
					return this
				}
			}
		}
	}
	return this
}

func (this *QProcess) makeKeyboard() Keyboard {
	keyboard := Keyboard{}
	for i := range this.next.PossibleAnswers {
		keyboard = append(keyboard, []string{this.next.PossibleAnswers[i]})
	}
	return keyboard
}

func (this *QProcess) andAsk() {
	if this.next != nil {

		//context
		for i := range this.next.Context {
			this.Details.send(this.next.Context[i])
		}
		//the actual question
		this.Details.sendWithKeyboard(this.next.QuestionText, this.makeKeyboard())
	}

	return
}
<commit_msg>fix loading of langFile<commit_after>package lib

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

const chat_cmd, ask_cmd, tell_cmd, help_cmd = "/chat", "/ask", "/tell", "/help"

type QProcess struct {
	Details *Details
	lang    struct {
		questions `json:"QandA"`
	}
	next *Question
}

func NewQProcess(d *Details) *QProcess {
	return &QProcess{Details: d}
}

func (this *QProcess) Run(input <-chan telebot.Message) {
	for msg := range input {
		this.Details.User = msg.Chat
		this.saveAndFindNext(msg).andAsk()
	}
}

func (this *QProcess) loadLanguage(name string) {

	file, err := os.Open(fmt.Sprintf("./config/%s.json", name))
	if err != nil {
		log.Panic("could not load QandA language file ", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.lang)
	if err != nil {
		log.Panic("could not decode QandA ", err.Error())
	}
}

func hasSubmisson(txt string, cmds ...string) bool {
	if isCmd(txt, cmds...) {
		for i := range cmds {
			log.Println("Check if submisson", txt)
			if len(strings.SplitAfter(txt, cmds[i])) > 1 {
				return true
			}
		}
	}
	return false
}

func isCmd(txt string, cmds ...string) bool {
	log.Println("Check if cmd", txt)
	for i := range cmds {
		if strings.Contains(txt, cmds[i]) {
			return true
		}
	}
	return false
}

func (this *QProcess) saveAndFindNext(msg telebot.Message) *QProcess {
	this.next = nil

	if hasSubmisson(msg.Text, help_cmd, ask_cmd, tell_cmd) {
		this.Details.save(msg)
		langFile := strings.SplitAfter(msg.Text, "/")[1]
		langFile = strings.Fields(langFile)[0]
		log.Println("loading ...", langFile)
		this.loadLanguage(langFile)
		this.next = this.lang.questions[len(this.lang.questions)-1]
	} else if isCmd(msg.Text, help_cmd, ask_cmd, tell_cmd, chat_cmd) {
		langFile := strings.SplitAfter(msg.Text, "/")[1]
		log.Println("loading ...", langFile)
		this.loadLanguage(langFile)
		this.next = this.lang.questions[0]
	} else {
		for i := range this.lang.questions {
			for a := range this.lang.questions[i].RelatesTo.Answers {
				if this.lang.questions[i].RelatesTo.Answers[a] == msg.Text {
					//was the answer a remainder to save?
					if this.lang.questions[i].RelatesTo.Save {
						this.Details.save(msg)
					}
					this.next = this.lang.questions[i]
					return this
				}
			}
		}
	}
	return this
}

func (this *QProcess) makeKeyboard() Keyboard {
	keyboard := Keyboard{}
	for i := range this.next.PossibleAnswers {
		keyboard = append(keyboard, []string{this.next.PossibleAnswers[i]})
	}
	return keyboard
}

func (this *QProcess) andAsk() {
	if this.next != nil {

		//context
		for i := range this.next.Context {
			this.Details.send(this.next.Context[i])
		}
		//the actual question
		this.Details.sendWithKeyboard(this.next.QuestionText, this.makeKeyboard())
	}

	return
}
<|endoftext|>


<commit_before>//manager a cluster of proxy
package manager

import (
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type Proxy struct {
	Link          string
	LastHeartBeat time.Time
}

var proxies map[string]*Proxy
var proxyList []*Proxy

func init() {
	proxies = make(map[string]*Proxy)
	proxyList = make([]*Proxy, 0, 10)
}

func checkProxy(link string) bool {
	proxy, err := url.Parse(link)
	if err != nil {
		return false
	}
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
		},
	}
	resp, err := client.Get("http://127.0.0.1:7183/check")
	if err != nil {
		return false
	}
	if resp == nil {
		return false
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false
	}
	t := strings.Trim(string(b), " \n\t\r")
	log.Println(t)
	return strings.Contains(link, t)
}

func Register(link string) {
	if _, ok := proxies[link]; ok {
		return
	}
	if !checkProxy(link) {
		return
	}
	p := &Proxy{
		Link:          link,
		LastHeartBeat: time.Now(),
	}
	proxies[link] = p
	proxyList = append(proxyList, p)
}

func HeartBeat(link string) {
	if p, ok := proxies[link]; ok {
		p.LastHeartBeat = time.Now()
	}
}

func Select() *Proxy {
	for i := 0; i < len(proxyList) && i < 3; i++ {
		k := rand.Intn(len(proxyList))
		if time.Now().Sub(proxyList[k].LastHeartBeat).Minutes() < 5 {
			return proxyList[k]
		}
	}
	return nil
}

func HandleRegister(rw http.ResponseWriter, req *http.Request) {
	params := req.URL.Query()
	proxy := params.Get("proxy")
	Register(proxy)
	fmt.Fprint(rw, "ok")
}

func HandleHeartBeat(rw http.ResponseWriter, req *http.Request) {
	params := req.URL.Query()
	proxy := params.Get("proxy")
	HeartBeat(proxy)
	fmt.Fprint(rw, "ok")
}

func HandleSelect(rw http.ResponseWriter, req *http.Request) {
	p := Select()
	if p != nil {
		fmt.Fprint(rw, p.Link)
	}
}

func HandleCheck(rw http.ResponseWriter, req *http.Request) {
	tks := strings.Split(req.RemoteAddr, ":")
	fmt.Fprint(rw, tks[0])
}
<commit_msg>manager<commit_after>//manager a cluster of proxy
package manager

import (
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type Proxy struct {
	Link          string
	LastHeartBeat time.Time
}

var proxies map[string]*Proxy
var proxyList []*Proxy

func init() {
	proxies = make(map[string]*Proxy)
	proxyList = make([]*Proxy, 0, 10)
}

func checkProxy(link string) bool {
	log.Println("begin check:", link)
	proxy, err := url.Parse(link)
	if err != nil {
		return false
	}
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxy),
		},
	}
	resp, err := client.Get("http://54.223.171.0:7183/check")
	if err != nil {
		return false
	}
	if resp == nil {
		return false
	}
	defer resp.Body.Close()
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false
	}
	t := strings.Trim(string(b), " \n\t\r")
	log.Println(t)
	return strings.Contains(link, t)
}

func Register(link string) {
	if _, ok := proxies[link]; ok {
		return
	}
	if !checkProxy(link) {
		return
	}
	p := &Proxy{
		Link:          link,
		LastHeartBeat: time.Now(),
	}
	proxies[link] = p
	proxyList = append(proxyList, p)
}

func HeartBeat(link string) {
	if p, ok := proxies[link]; ok {
		p.LastHeartBeat = time.Now()
	}
}

func Select() *Proxy {
	for i := 0; i < len(proxyList) && i < 3; i++ {
		k := rand.Intn(len(proxyList))
		if time.Now().Sub(proxyList[k].LastHeartBeat).Minutes() < 5 {
			return proxyList[k]
		}
	}
	return nil
}

func HandleRegister(rw http.ResponseWriter, req *http.Request) {
	params := req.URL.Query()
	proxy := params.Get("proxy")
	Register(proxy)
	fmt.Fprint(rw, "ok")
}

func HandleHeartBeat(rw http.ResponseWriter, req *http.Request) {
	params := req.URL.Query()
	proxy := params.Get("proxy")
	HeartBeat(proxy)
	fmt.Fprint(rw, "ok")
}

func HandleSelect(rw http.ResponseWriter, req *http.Request) {
	p := Select()
	if p != nil {
		fmt.Fprint(rw, p.Link)
	}
}

func HandleCheck(rw http.ResponseWriter, req *http.Request) {
	tks := strings.Split(req.RemoteAddr, ":")
	fmt.Fprint(rw, tks[0])
}
<|endoftext|>


<commit_before>package cluster

import (
	"errors"
	"sync"
	"time"

	"github.com/flynn/flynn-host/types"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-flynn/attempt"
	"github.com/flynn/rpcplus"
)

// ErrNoServers is returned if no host servers are found
var ErrNoServers = errors.New("cluster: no servers found")

// Attempts is the attempt strategy that is used to connect to the leader.
// It must not be modified after the first call to NewClient.
var Attempts = attempt.Strategy{
	Min:   5,
	Total: 5 * time.Second,
	Delay: 200 * time.Millisecond,
}

func NewClient() (*Client, error) {
	return NewClientWithDial(nil, nil)
}

func NewClientWithDial(dial rpcplus.DialFunc, services func(name string) (discoverd.ServiceSet, error)) (*Client, error) {
	if services == nil {
		services = discoverd.NewServiceSet
	}
	ss, err := services("flynn-host")
	if err != nil {
		return nil, err
	}
	client := &Client{dial: dial, service: ss}
	firstErr := make(chan error)
	go client.followLeader(firstErr)
	return client, <-firstErr
}

func (c *Client) followLeader(firstErr chan<- error) {
	for update := range c.service.Leaders() {
		if update == nil {
			if firstErr != nil {
				firstErr <- ErrNoServers
				firstErr = nil
			}
			continue
		}
		c.mtx.Lock()
		if c.c != nil {
			c.c.Close()
		}
		c.err = Attempts.Run(func() (err error) {
			c.c, err = rpcplus.DialHTTPPath("tcp", update.Addr, rpcplus.DefaultRPCPath, c.dial)
			return
		})
		c.mtx.Unlock()
		if firstErr != nil {
			firstErr <- c.err
			firstErr = nil
		}
	}
	// TODO: reconnect to discoverd here
}

type Client struct {
	service discoverd.ServiceSet

	dial rpcplus.DialFunc
	c    RPCClient
	mtx  sync.RWMutex
	err  error
}

func (c *Client) Close() error {
	c.service.Close()
	return c.c.Close()
}

func (c *Client) ListHosts() (map[string]host.Host, error) {
	c.mtx.RLock()
	if err := c.err; err != nil {
		c.mtx.RUnlock()
		return nil, err
	}
	client := c.c
	c.mtx.RUnlock()

	var state map[string]host.Host
	err := client.Call("Cluster.ListHosts", struct{}{}, &state)
	return state, err
}

func (c *Client) AddJobs(req *host.AddJobsReq) (*host.AddJobsRes, error) {
	c.mtx.RLock()
	if err := c.err; err != nil {
		c.mtx.RUnlock()
		return nil, err
	}
	client := c.c
	c.mtx.RUnlock()

	var res host.AddJobsRes
	err := client.Call("Cluster.AddJobs", req, &res)
	return &res, err
}

func (c *Client) ConnectHost(id string) (Host, error) {
	// TODO: reuse connection if leader id == id
	services := c.service.Select(map[string]string{"id": id})
	if len(services) == 0 {
		return nil, ErrNoServers
	}
	rc, err := rpcplus.DialHTTPPath("tcp", services[0].Addr, rpcplus.DefaultRPCPath, c.dial)
	return newHostClient(c.service, rc, c.dial), err
}

func (c *Client) RPCClient() (RPCClient, error) {
	c.mtx.RLock()
	defer c.mtx.RUnlock()
	return c.c, c.err
}

type RPCClient interface {
	Call(serviceMethod string, args interface{}, reply interface{}) error
	Go(serviceMethod string, args interface{}, reply interface{}, done chan *rpcplus.Call) *rpcplus.Call
	StreamGo(serviceMethod string, args interface{}, replyStream interface{}) *rpcplus.Call
	Close() error
}
<commit_msg>pkg/cluster: Add LeaderID accessor<commit_after>package cluster

import (
	"errors"
	"sync"
	"time"

	"github.com/flynn/flynn-host/types"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-flynn/attempt"
	"github.com/flynn/rpcplus"
)

// ErrNoServers is returned if no host servers are found
var ErrNoServers = errors.New("cluster: no servers found")

// Attempts is the attempt strategy that is used to connect to the leader.
// It must not be modified after the first call to NewClient.
var Attempts = attempt.Strategy{
	Min:   5,
	Total: 5 * time.Second,
	Delay: 200 * time.Millisecond,
}

func NewClient() (*Client, error) {
	return NewClientWithDial(nil, nil)
}

func NewClientWithDial(dial rpcplus.DialFunc, services func(name string) (discoverd.ServiceSet, error)) (*Client, error) {
	if services == nil {
		services = discoverd.NewServiceSet
	}
	ss, err := services("flynn-host")
	if err != nil {
		return nil, err
	}
	client := &Client{dial: dial, service: ss}
	firstErr := make(chan error)
	go client.followLeader(firstErr)
	return client, <-firstErr
}

func (c *Client) followLeader(firstErr chan<- error) {
	for update := range c.service.Leaders() {
		if update == nil {
			if firstErr != nil {
				firstErr <- ErrNoServers
				firstErr = nil
			}
			continue
		}
		c.mtx.Lock()
		if c.c != nil {
			c.c.Close()
		}
		c.leaderID = update.Attrs["id"]
		c.err = Attempts.Run(func() (err error) {
			c.c, err = rpcplus.DialHTTPPath("tcp", update.Addr, rpcplus.DefaultRPCPath, c.dial)
			return
		})
		c.mtx.Unlock()
		if firstErr != nil {
			firstErr <- c.err
			firstErr = nil
		}
	}
	// TODO: reconnect to discoverd here
}

type Client struct {
	service  discoverd.ServiceSet
	leaderID string

	dial rpcplus.DialFunc
	c    RPCClient
	mtx  sync.RWMutex
	err  error
}

func (c *Client) Close() error {
	c.service.Close()
	return c.c.Close()
}

func (c *Client) LeaderID() string {
	c.mtx.RLock()
	defer c.mtx.RUnlock()
	return c.leaderID
}

func (c *Client) ListHosts() (map[string]host.Host, error) {
	c.mtx.RLock()
	if err := c.err; err != nil {
		c.mtx.RUnlock()
		return nil, err
	}
	client := c.c
	c.mtx.RUnlock()

	var state map[string]host.Host
	err := client.Call("Cluster.ListHosts", struct{}{}, &state)
	return state, err
}

func (c *Client) AddJobs(req *host.AddJobsReq) (*host.AddJobsRes, error) {
	c.mtx.RLock()
	if err := c.err; err != nil {
		c.mtx.RUnlock()
		return nil, err
	}
	client := c.c
	c.mtx.RUnlock()

	var res host.AddJobsRes
	err := client.Call("Cluster.AddJobs", req, &res)
	return &res, err
}

func (c *Client) ConnectHost(id string) (Host, error) {
	// TODO: reuse connection if leader id == id
	services := c.service.Select(map[string]string{"id": id})
	if len(services) == 0 {
		return nil, ErrNoServers
	}
	rc, err := rpcplus.DialHTTPPath("tcp", services[0].Addr, rpcplus.DefaultRPCPath, c.dial)
	return newHostClient(c.service, rc, c.dial), err
}

func (c *Client) RPCClient() (RPCClient, error) {
	c.mtx.RLock()
	defer c.mtx.RUnlock()
	return c.c, c.err
}

type RPCClient interface {
	Call(serviceMethod string, args interface{}, reply interface{}) error
	Go(serviceMethod string, args interface{}, reply interface{}, done chan *rpcplus.Call) *rpcplus.Call
	StreamGo(serviceMethod string, args interface{}, replyStream interface{}) *rpcplus.Call
	Close() error
}
<|endoftext|>


<commit_before>package marshal

import (
	"errors"
	"strconv"
)

type MarshalledObject struct {
	MajorVersion byte
	MinorVersion byte
	data         []byte
  symbolCache  *[]string
}

type marshalledObjectType byte

var TypeMismatch = errors.New("gorails/marshal: an attempt to implicitly typecast a marshalled object")
var IncompleteData = errors.New("gorails/marshal: incomplete data")

const (
	TYPE_UNKNOWN marshalledObjectType = 0
	TYPE_NIL     marshalledObjectType = 1
	TYPE_BOOL    marshalledObjectType = 2
	TYPE_INTEGER marshalledObjectType = 3
	TYPE_FLOAT   marshalledObjectType = 4
	TYPE_STRING  marshalledObjectType = 5
	TYPE_ARRAY   marshalledObjectType = 6
	TYPE_MAP     marshalledObjectType = 7
)

func newMarshalledObject(major_version, minor_version byte, data []byte, symbolCache *[]string) *MarshalledObject {
	return &(MarshalledObject{major_version, minor_version, data, symbolCache})
}

func CreateMarshalledObject(serialized_data []byte) *MarshalledObject {
	cache := []string{}
	return newMarshalledObject(serialized_data[0], serialized_data[1], serialized_data[2:], &cache)
}

func (obj *MarshalledObject) GetType() marshalledObjectType {
	if len(obj.data) == 0 {
		return TYPE_UNKNOWN
	}

	switch obj.data[0] {
	case '0':
		return TYPE_NIL
	case 'T', 'F':
		return TYPE_BOOL
	case 'i':
		return TYPE_INTEGER
	case 'f':
		return TYPE_FLOAT
	case ':', ';':
		return TYPE_STRING
	case 'I':
		if len(obj.data) > 1 && obj.data[1] == '"' {
			return TYPE_STRING
		}
	case '[':
		return TYPE_ARRAY
	case '{':
		return TYPE_MAP
	}

	return TYPE_UNKNOWN
}

func (obj *MarshalledObject) GetAsBool() (value bool, err error) {
	err = assertType(obj, TYPE_BOOL)
	if err != nil {
		return
	}

	value, _ = parseBool(obj.data)

	return
}

func (obj *MarshalledObject) GetAsInteger() (value int64, err error) {
	err = assertType(obj, TYPE_INTEGER)
	if err != nil {
		return
	}

	value, _ = parseInt(obj.data[1:])

	return
}

func (obj *MarshalledObject) GetAsFloat() (value float64, err error) {
	err = assertType(obj, TYPE_FLOAT)
	if err != nil {
		return
	}

	str, _ := parseString(obj.data[1:])
	value, err = strconv.ParseFloat(str, 64)

	return
}

func (obj *MarshalledObject) GetAsString() (value string, err error) {
	err = assertType(obj, TYPE_STRING)
	if err != nil {
		return
	}

	var cache []string
  if obj.data[0] == ':' {
		value, _ = parseString(obj.data[1:])
		obj.cacheSymbols(value)
  } else if obj.data[0] == ';' {
  	ref_index, _ := parseInt(obj.data[1:])
    cache := *(obj.symbolCache)
    value = cache[ref_index]
	} else {
		value, _, cache = parseStringWithEncoding(obj.data[2:])
		obj.cacheSymbols(cache...)
	}

	return
}

func (obj *MarshalledObject) GetAsArray() (value []*MarshalledObject, err error) {
	err = assertType(obj, TYPE_ARRAY)
	if err != nil {
		return
	}

	array_size, offset := parseInt(obj.data[1:])
  offset += 1

	value = make([]*MarshalledObject, array_size)
	for i := int64(0); i < array_size; i++ {
		value_size := newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:],
      obj.symbolCache,
		).getSize()

		value[i] = newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:offset+value_size],
      obj.symbolCache,
		)
		offset += value_size
	}

	return
}

func (obj *MarshalledObject) GetAsMap() (value map[string]*MarshalledObject, err error) {
	err = assertType(obj, TYPE_MAP)
	if err != nil {
		return
	}

	map_size, offset := parseInt(obj.data[1:])
	offset += 1

	value = make(map[string]*MarshalledObject, map_size)
	for i := int64(0); i < map_size; i++ {

		k := newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:],
      obj.symbolCache,
		)
		offset += k.getSize()

		value_size := newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:],
      obj.symbolCache,
		).getSize()

		v := newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:offset+value_size],
      obj.symbolCache,
		)
		value[k.toString()] = v

		offset += value_size
	}

	return
}

func assertType(obj *MarshalledObject, expected_type marshalledObjectType) (err error) {
	if obj.GetType() != expected_type {
		err = TypeMismatch
	}

	return
}

func (obj *MarshalledObject) getSize() int {
	header_size, data_size := 0, 0

	switch obj.GetType() {
	case TYPE_NIL, TYPE_BOOL:
		header_size = 0
		data_size   = 1
	case TYPE_INTEGER:
		header_size = 1
		_, data_size = parseInt(obj.data[header_size:])
	case TYPE_STRING, TYPE_FLOAT:
		header_size = 1

		if obj.data[0] == ';' {
			_, data_size = parseInt(obj.data[header_size:])
		} else {
			var cache []string

			if obj.data[0] == 'I' {
				header_size += 1
				_, data_size, cache = parseStringWithEncoding(obj.data[header_size:])
				obj.cacheSymbols(cache...)
			} else {
				var symbol string
				symbol, data_size = parseString(obj.data[header_size:])
				obj.cacheSymbols(symbol)
			}
		}
	}

	return header_size + data_size
}

func (obj *MarshalledObject) cacheSymbols(symbols ...string) {
	if len(symbols) == 0 {
		return
	}

	cache := *(obj.symbolCache)

	known := make(map[string]struct{})
	for _, symbol := range cache {
		known[symbol] = struct{}{}
	}

	for _, symbol := range symbols {
		_, exists := known[symbol]

		if ! exists {
			cache = append(cache, symbol)
		}
	}

	*(obj.symbolCache) = cache

}

func (obj *MarshalledObject) toString() (str string) {
	switch obj.GetType() {
	case TYPE_NIL:
		str = "<nil>"
	case TYPE_BOOL:
		v, _ := obj.GetAsBool()

		if v {
			str = "true"
		} else {
			str = "false"
		}
	case TYPE_INTEGER:
		v, _ := obj.GetAsInteger()
		str = strconv.FormatInt(v, 10)
	case TYPE_STRING:
		str, _ = obj.GetAsString()
	case TYPE_FLOAT:
		v, _ := obj.GetAsFloat()
		str = strconv.FormatFloat(v, 'f', -1, 64)
	}

	return
}

func parseBool(data []byte) (bool, int) {
	return data[0] == 'T', 1
}

func parseInt(data []byte) (int64, int) {
	if data[0] > 0x05 && data[0] < 0xfb {
		value := int64(data[0])

		if value > 0x7f {
			return -(0xff ^ value + 1) + 5, 1
		} else {
			return value - 5, 1
		}
	} else if data[0] <= 0x05 {
		value := int64(0)
		i := data[0]

		for ; i > 0; i-- {
			value = value<<8 + int64(data[i])
		}

		return value, int(data[0] + 1)
	} else {
		value := int64(0)
		i := 0xff - data[0] + 1

		for ; i > 0; i-- {
			value = value<<8 + (0xff - int64(data[i]))
		}

		return -(value + 1), int(0xff - data[0] + 2)
	}
}

func parseString(data []byte) (string, int) {
	length, header_size := parseInt(data)
	size := int(length) + header_size

  return string(data[header_size : size]), size
}

func parseStringWithEncoding(data []byte) (string, int, []string) {
	cache := make([]string, 0)
  value, size := parseString(data)

  // May fail if string is followed by a symbol
	if len(data) > size+1 && (data[size+1] == ':' || data[size+1] == ';') {
    if data[size+1] == ';' {
      _, enc_size := parseInt(data[size+2:])
      size += enc_size + 1 // reference to a symbol
    } else {
      enc_symbol, enc_size := parseString(data[size+2:])
      size += enc_size + 1
      cache = append(cache, enc_symbol)
    }

    if data[size+1] == '"' {
      encoding, enc_name_size := parseString(data[size+2:])
      _ = encoding
      size += enc_name_size + 1
		} else {
			_, enc_name_size := parseBool(data[size+1:])
			size += enc_name_size
		}

		size += 1
	}

	return value, size, cache
}
<commit_msg>Handling recursive types (i.e. arrays of maps, etc.)<commit_after>package marshal

import (
	"errors"
	"strconv"
)

type MarshalledObject struct {
	MajorVersion byte
	MinorVersion byte

	data         []byte
  symbolCache  *[]string
  size         int
}

type marshalledObjectType byte

var TypeMismatch = errors.New("gorails/marshal: an attempt to implicitly typecast a marshalled object")
var IncompleteData = errors.New("gorails/marshal: incomplete data")

const (
	TYPE_UNKNOWN marshalledObjectType = 0
	TYPE_NIL     marshalledObjectType = 1
	TYPE_BOOL    marshalledObjectType = 2
	TYPE_INTEGER marshalledObjectType = 3
	TYPE_FLOAT   marshalledObjectType = 4
	TYPE_STRING  marshalledObjectType = 5
	TYPE_ARRAY   marshalledObjectType = 6
	TYPE_MAP     marshalledObjectType = 7
)

func newMarshalledObject(major_version, minor_version byte, data []byte, symbolCache *[]string) *MarshalledObject {
	return newMarshalledObjectWithSize(major_version, minor_version, data, len(data), symbolCache)
}

func newMarshalledObjectWithSize(major_version, minor_version byte, data []byte, size int, symbolCache *[]string) *MarshalledObject {
	return &(MarshalledObject{major_version, minor_version, data, symbolCache, size})
}

func CreateMarshalledObject(serialized_data []byte) *MarshalledObject {
	cache := []string{}
	return newMarshalledObject(serialized_data[0], serialized_data[1], serialized_data[2:], &cache)
}

func (obj *MarshalledObject) GetType() marshalledObjectType {
	if len(obj.data) == 0 {
		return TYPE_UNKNOWN
	}

	switch obj.data[0] {
	case '0':
		return TYPE_NIL
	case 'T', 'F':
		return TYPE_BOOL
	case 'i':
		return TYPE_INTEGER
	case 'f':
		return TYPE_FLOAT
	case ':', ';':
		return TYPE_STRING
	case 'I':
		if len(obj.data) > 1 && obj.data[1] == '"' {
			return TYPE_STRING
		}
	case '[':
		return TYPE_ARRAY
	case '{':
		return TYPE_MAP
	}

	return TYPE_UNKNOWN
}

func (obj *MarshalledObject) GetAsBool() (value bool, err error) {
	err = assertType(obj, TYPE_BOOL)
	if err != nil {
		return
	}

	value, _ = parseBool(obj.data)

	return
}

func (obj *MarshalledObject) GetAsInteger() (value int64, err error) {
	err = assertType(obj, TYPE_INTEGER)
	if err != nil {
		return
	}

	value, _ = parseInt(obj.data[1:])

	return
}

func (obj *MarshalledObject) GetAsFloat() (value float64, err error) {
	err = assertType(obj, TYPE_FLOAT)
	if err != nil {
		return
	}

	str, _ := parseString(obj.data[1:])
	value, err = strconv.ParseFloat(str, 64)

	return
}

func (obj *MarshalledObject) GetAsString() (value string, err error) {
	err = assertType(obj, TYPE_STRING)
	if err != nil {
		return
	}

	var cache []string
  if obj.data[0] == ':' {
		value, _ = parseString(obj.data[1:])
		obj.cacheSymbols(value)
  } else if obj.data[0] == ';' {
  	ref_index, _ := parseInt(obj.data[1:])
    cache := *(obj.symbolCache)
    value = cache[ref_index]
	} else {
		value, _, cache = parseStringWithEncoding(obj.data[2:])
		obj.cacheSymbols(cache...)
	}

	return
}

func (obj *MarshalledObject) GetAsArray() (value []*MarshalledObject, err error) {
	err = assertType(obj, TYPE_ARRAY)
	if err != nil {
		return
	}

	array_size, offset := parseInt(obj.data[1:])
  offset += 1

	value = make([]*MarshalledObject, array_size)
	for i := int64(0); i < array_size; i++ {
		value_size := newMarshalledObjectWithSize(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:],
			0,
      obj.symbolCache,
		).getSize()

		value[i] = newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:offset+value_size],
      obj.symbolCache,
		)
		offset += value_size
	}

	obj.size = offset

	return
}

func (obj *MarshalledObject) GetAsMap() (value map[string]*MarshalledObject, err error) {
	err = assertType(obj, TYPE_MAP)
	if err != nil {
		return
	}

	map_size, offset := parseInt(obj.data[1:])
	offset += 1

	value = make(map[string]*MarshalledObject, map_size)
	for i := int64(0); i < map_size; i++ {
		k := newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:],
      obj.symbolCache,
		)
		offset += k.getSize()

		value_size := newMarshalledObjectWithSize(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:],
			0,
      obj.symbolCache,
		).getSize()

		v := newMarshalledObject(
			obj.MajorVersion,
			obj.MinorVersion,
			obj.data[offset:offset+value_size],
      obj.symbolCache,
		)
		value[k.toString()] = v

		offset += value_size
	}

	obj.size = offset

	return
}

func assertType(obj *MarshalledObject, expected_type marshalledObjectType) (err error) {
	if obj.GetType() != expected_type {
		err = TypeMismatch
	}

	return
}

func (obj *MarshalledObject) getSize() int {
	header_size, data_size := 0, 0

	switch obj.GetType() {
	case TYPE_NIL, TYPE_BOOL:
		header_size = 0
		data_size   = 1
	case TYPE_INTEGER:
		header_size = 1
		_, data_size = parseInt(obj.data[header_size:])
	case TYPE_STRING, TYPE_FLOAT:
		header_size = 1

		if obj.data[0] == ';' {
			_, data_size = parseInt(obj.data[header_size:])
		} else {
			var cache []string

			if obj.data[0] == 'I' {
				header_size += 1
				_, data_size, cache = parseStringWithEncoding(obj.data[header_size:])
				obj.cacheSymbols(cache...)
			} else {
				var symbol string
				symbol, data_size = parseString(obj.data[header_size:])
				obj.cacheSymbols(symbol)
			}
		}
	case TYPE_ARRAY:
		if obj.size == 0 {
			obj.GetAsArray()
		}

		return obj.size
	case TYPE_MAP:
		if obj.size == 0 {
			obj.GetAsMap()
		}

		return obj.size
	}

	return header_size + data_size
}

func (obj *MarshalledObject) cacheSymbols(symbols ...string) {
	if len(symbols) == 0 {
		return
	}

	cache := *(obj.symbolCache)

	known := make(map[string]struct{})
	for _, symbol := range cache {
		known[symbol] = struct{}{}
	}

	for _, symbol := range symbols {
		_, exists := known[symbol]

		if ! exists {
			cache = append(cache, symbol)
		}
	}

	*(obj.symbolCache) = cache
}

func (obj *MarshalledObject) toString() (str string) {
	switch obj.GetType() {
	case TYPE_NIL:
		str = "<nil>"
	case TYPE_BOOL:
		v, _ := obj.GetAsBool()

		if v {
			str = "true"
		} else {
			str = "false"
		}
	case TYPE_INTEGER:
		v, _ := obj.GetAsInteger()
		str = strconv.FormatInt(v, 10)
	case TYPE_STRING:
		str, _ = obj.GetAsString()
	case TYPE_FLOAT:
		v, _ := obj.GetAsFloat()
		str = strconv.FormatFloat(v, 'f', -1, 64)
	}

	return
}

func parseBool(data []byte) (bool, int) {
	return data[0] == 'T', 1
}

func parseInt(data []byte) (int64, int) {
	if data[0] > 0x05 && data[0] < 0xfb {
		value := int64(data[0])

		if value > 0x7f {
			return -(0xff ^ value + 1) + 5, 1
		} else {
			return value - 5, 1
		}
	} else if data[0] <= 0x05 {
		value := int64(0)
		i := data[0]

		for ; i > 0; i-- {
			value = value<<8 + int64(data[i])
		}

		return value, int(data[0] + 1)
	} else {
		value := int64(0)
		i := 0xff - data[0] + 1

		for ; i > 0; i-- {
			value = value<<8 + (0xff - int64(data[i]))
		}

		return -(value + 1), int(0xff - data[0] + 2)
	}
}

func parseString(data []byte) (string, int) {
	length, header_size := parseInt(data)
	size := int(length) + header_size

  return string(data[header_size : size]), size
}

func parseStringWithEncoding(data []byte) (string, int, []string) {
	cache := make([]string, 0)
  value, size := parseString(data)

  // May fail if string is followed by a symbol
	if len(data) > size+1 && (data[size+1] == ':' || data[size+1] == ';') {
    if data[size+1] == ';' {
      _, enc_size := parseInt(data[size+2:])
      size += enc_size + 1 // reference to a symbol
    } else {
      enc_symbol, enc_size := parseString(data[size+2:])
      size += enc_size + 1
      cache = append(cache, enc_symbol)
    }

    if data[size+1] == '"' {
      encoding, enc_name_size := parseString(data[size+2:])
      _ = encoding
      size += enc_name_size + 1
		} else {
			_, enc_name_size := parseBool(data[size+1:])
			size += enc_name_size
		}

		size += 1
	}

	return value, size, cache
}
<|endoftext|>