<commit_before>package core

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sync"

	"github.com/tinzenite/channel"
	"github.com/tinzenite/model"
	"github.com/tinzenite/shared"
)

/*
Tinzenite is the struct on which all important operations should be called.
*/
type Tinzenite struct {
	Path           string
	auth           *Authentication
	selfpeer       *shared.Peer
	channel        *channel.Channel
	cInterface     *chaninterface
	allPeers       []*shared.Peer
	model          *model.Model
	sendChannel    chan shared.UpdateMessage
	muteFlag       bool
	stop           chan bool
	wg             sync.WaitGroup
	peerValidation PeerValidation
}

/*
SyncRemote updates first locally and then sync remotely if other peers are
connected. NOTE: Both sync methods can block for a potentially long time,
especially when first run!
*/
func (t *Tinzenite) SyncRemote() error {
	// mute updates because we'll sync models later
	t.muteFlag = true
	// defer setting it back guaranteed
	defer func() { t.muteFlag = false }()
	// first ensure that local model is up to date
	err := t.SyncLocal()
	if err != nil {
		return err
	}
	online, err := t.channel.OnlineAddresses()
	if err != nil {
		return err
	}
	for _, address := range online {
		t.cInterface.SyncModel(address)
	}
	return nil
}

/*
SyncLocal changes. Will send updates to connected peers but not synchronize with
other peers.
*/
func (t *Tinzenite) SyncLocal() error {
	// update peers
	err := t.applyPeers()
	if err != nil {
		return err
	}
	// update model
	return t.model.Update()
}

/*
Address of this Tinzenite peer that can be used to connect to.
*/
func (t *Tinzenite) Address() (string, error) {
	return t.channel.ConnectionAddress()
}

/*
Name of this Tinzenite peer.
*/
func (t *Tinzenite) Name() string {
	return t.selfpeer.Name
}

/*
Close cleanly stores everything and shuts Tinzenite down.
*/
func (t *Tinzenite) Close() {
	// send stop signal
	t.stop <- false
	// wait for it to close
	t.wg.Wait()
	// store all information
	t.Store()
	// FINALLY close (afterwards because I still need info from channel for store!)
	t.channel.Close()
}

/*
Store the tinzenite directory structure to disk. Will resolve all important
objects and store them so that it can later be reloaded. NOTE: Will not update
the full model, so be sure to have called Update() to guarantee an up to date
save.
*/
func (t *Tinzenite) Store() error {
	err := shared.MakeDotTinzenite(t.Path)
	if err != nil {
		return err
	}
	// write all peers to files
	for _, peer := range t.allPeers {
		err := peer.Store(t.Path)
		if err != nil {
			return err
		}
	}
	// store local peer info with toxdata
	toxData, err := t.channel.ToxData()
	if err != nil {
		return err
	}
	toxPeerDump := &shared.ToxPeerDump{
		SelfPeer: t.selfpeer,
		ToxData:  toxData}
	err = toxPeerDump.Store(t.Path)
	if err != nil {
		return err
	}
	// store auth file
	err = t.auth.Store(t.Path)
	if err != nil {
		return err
	}
	// update model for tinzenite dir to catch above stores
	err = t.model.PartialUpdate(t.Path + "/" + shared.TINZENITEDIR)
	if err != nil {
		return err
	}
	// finally store model (last because peers and org stuff is included!)
	return t.model.Store()
}

/*
PrintStatus returns a formatted string of the peer status.
*/
func (t *Tinzenite) PrintStatus() string {
	var out string
	out += "Online:\n"
	addresses, err := t.channel.FriendAddresses()
	if err != nil {
		out += "channel.FriendAddresses failed!"
	} else {
		var count int
		for _, address := range addresses {
			online, err := t.channel.IsAddressOnline(address)
			var insert string
			if err != nil {
				insert = "ERROR"
			} else {
				insert = fmt.Sprintf("%v", online)
			}
			out += address[:16] + " :: " + insert + "\n"
			count++
		}
		out += "Total friends: " + fmt.Sprintf("%d", count)
	}
	return out
}

/*
DisconnectPeer does exactly that. NOTE: this is a passive action and doesn't do
anything except remove the peer from the network. The peer is not further
notified.

TODO: maybe not use name but Identification?
TODO: must write peer to all ongoing removals to ensure that they aren't orphaned!
*/
func (t *Tinzenite) DisconnectPeer(peerName string) {
	var newPeers []*shared.Peer
	for _, peer := range t.allPeers {
		if t.selfpeer.Identification == peer.Identification {
			continue
		}
		if peer.Name == peerName {
			log.Println("Removing", peer.Name, "at", peer.Address[:8])
			// delete peer file
			path := shared.CreatePath(t.Path, shared.TINZENITEDIR+"/"+shared.ORGDIR+"/"+shared.PEERSDIR+"/"+peer.Identification+shared.ENDING)
			err := t.model.ApplyRemove(path, nil)
			if err != nil {
				log.Println("DisconnectPeer:", err)
			}
			// remove from channel
			err = t.channel.RemoveConnection(peer.Address)
			if err != nil {
				log.Println("DisconnectPeer:", err)
			}
			// write peer to all removals so that no removals will be orphaned
			removePath := t.Path + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR
			allRemovals, _ := ioutil.ReadDir(removePath)
			// for every object that is currently being removed
			for _, stat := range allRemovals {
				// write the to be removed peer as done
				err := t.model.UpdateRemovalDir(stat.Name(), peer.Identification)
				if err != nil {
					// warn if it failed
					log.Println("Tinzenite: failed to purge removed peer from removal!")
				}
			}
			// continue does not readd to tinzenite, removing the reference to it
			continue
		}
		newPeers = append(newPeers, peer)
	}
	t.allPeers = newPeers
}

/*
applyPeers loads all peers and readies the communication channel accordingly.
*/
func (t *Tinzenite) applyPeers() error {
	peers, err := shared.LoadPeers(t.Path)
	if err != nil {
		return err
	}
	// make sure they are all tox ready
	for _, peer := range peers {
		// tox will return an error if the address has already been added, so we just ignore it
		_ = t.channel.AcceptConnection(peer.Address)
	}
	// finally apply
	t.allPeers = peers
	return nil
}

/*
Send the given message to the peer with the address if online.
*/
func (t *Tinzenite) send(address, msg string) error {
	return t.channel.Send(address, msg)
}

/*
Merge an update message to the local model.
*/
func (t *Tinzenite) merge(msg *shared.UpdateMessage) error {
	relPath := shared.CreatePath(t.Path, msg.Object.Path)
	// first: apply local changes to model (this is why writing PartialUpdate was no waste of time, isn't this cool?! :D)
	err := t.model.PartialUpdate(relPath.FullPath())
	if err != nil {
		return err
	}
	// check if content is same, no need for merge then (except for version)
	stin, err := t.model.GetInfo(relPath)
	if err != nil {
		log.Println("Merge: can not check if content is same!")
	} else {
		if stin.Content == msg.Object.Content {
			// log.Println("Core:", "Merge not required as updates are in sync!")
			// so all we need to do is apply the version update
			return t.model.ApplyModify(relPath, &msg.Object)
		}
	}
	// second: move to new name
	err = os.Rename(relPath.FullPath(), relPath.FullPath()+LOCAL)
	if err != nil {
		log.Println("Merge: original can not be found!")
		return err
	}
	// third: apply create of local version
	localVersionPath := relPath.RenameLastElement(relPath.LastElement() + LOCAL)
	err = t.model.ApplyCreate(localVersionPath, nil)
	if err != nil {
		log.Println("Merge: creating local merge file failed!")
		return err
	}
	// fourth: remove original
	err = t.model.ApplyRemove(relPath, nil)
	if err != nil {
		log.Println("Merge: removing original failed!")
		return err
	}
	// fifth: change path and apply remote as create
	msg.Operation = shared.OpCreate
	msg.Object.Path = relPath.SubPath() + REMOTE
	msg.Object.Name = relPath.LastElement() + REMOTE
	oldID := msg.Object.Identification
	msg.Object.Identification, err = shared.NewIdentifier()
	if err != nil {
		log.Println("Merge: failed to create new identifier!")
		return err
	}
	// new id --> rename temp file
	tempPath := t.Path + "/" + shared.TINZENITEDIR + "/" + shared.TEMPDIR
	err = os.Rename(tempPath+"/"+oldID, tempPath+"/"+msg.Object.Identification)
	if err != nil {
		log.Println("Merge: ipdating remote object file failed!")
		return err
	}
	// sixth: create remote file
	err = t.model.ApplyCreate(relPath.Apply(relPath.FullPath()+REMOTE), &msg.Object)
	if err != nil {
		log.Println("Merge: creating remote merge file failed!")
		return err
	}
	return nil
}

/*
initialize the background process.
*/
func (t *Tinzenite) initialize() {
	// prepare send channel that will distribute updates
	t.wg.Add(1)
	t.stop = make(chan bool, 1)
	t.sendChannel = make(chan shared.UpdateMessage, 1)
	go t.background()
	t.model.Register(t.sendChannel)
}

/*
background function that handles all async stuff that needs to be done.
*/
func (t *Tinzenite) background() {
	defer func() { log.Println("Tinzenite: Background process stopped.") }()
	for {
		select {
		case <-t.stop:
			t.wg.Done()
			return
		case msg := <-t.sendChannel:
			// if muted don't send updates
			if t.muteFlag {
				continue
			}
			online, err := t.channel.OnlineAddresses()
			if err != nil {
				log.Println("Background:", err)
			}
			for _, address := range online {
				name := msg.Object.Name
				// for better visibility add special mark to signify directory
				if msg.Object.Directory {
					name += "/++"
				}
				log.Printf("Tin: sending <%s> of <.../%s> to %s.\n", msg.Operation, name, address[:8])
				t.channel.Send(address, msg.JSON())
			}
		} // select
	} // for
}
<commit_msg>removed done todo<commit_after>package core

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"sync"

	"github.com/tinzenite/channel"
	"github.com/tinzenite/model"
	"github.com/tinzenite/shared"
)

/*
Tinzenite is the struct on which all important operations should be called.
*/
type Tinzenite struct {
	Path           string
	auth           *Authentication
	selfpeer       *shared.Peer
	channel        *channel.Channel
	cInterface     *chaninterface
	allPeers       []*shared.Peer
	model          *model.Model
	sendChannel    chan shared.UpdateMessage
	muteFlag       bool
	stop           chan bool
	wg             sync.WaitGroup
	peerValidation PeerValidation
}

/*
SyncRemote updates first locally and then sync remotely if other peers are
connected. NOTE: Both sync methods can block for a potentially long time,
especially when first run!
*/
func (t *Tinzenite) SyncRemote() error {
	// mute updates because we'll sync models later
	t.muteFlag = true
	// defer setting it back guaranteed
	defer func() { t.muteFlag = false }()
	// first ensure that local model is up to date
	err := t.SyncLocal()
	if err != nil {
		return err
	}
	online, err := t.channel.OnlineAddresses()
	if err != nil {
		return err
	}
	for _, address := range online {
		t.cInterface.SyncModel(address)
	}
	return nil
}

/*
SyncLocal changes. Will send updates to connected peers but not synchronize with
other peers.
*/
func (t *Tinzenite) SyncLocal() error {
	// update peers
	err := t.applyPeers()
	if err != nil {
		return err
	}
	// update model
	return t.model.Update()
}

/*
Address of this Tinzenite peer that can be used to connect to.
*/
func (t *Tinzenite) Address() (string, error) {
	return t.channel.ConnectionAddress()
}

/*
Name of this Tinzenite peer.
*/
func (t *Tinzenite) Name() string {
	return t.selfpeer.Name
}

/*
Close cleanly stores everything and shuts Tinzenite down.
*/
func (t *Tinzenite) Close() {
	// send stop signal
	t.stop <- false
	// wait for it to close
	t.wg.Wait()
	// store all information
	t.Store()
	// FINALLY close (afterwards because I still need info from channel for store!)
	t.channel.Close()
}

/*
Store the tinzenite directory structure to disk. Will resolve all important
objects and store them so that it can later be reloaded. NOTE: Will not update
the full model, so be sure to have called Update() to guarantee an up to date
save.
*/
func (t *Tinzenite) Store() error {
	err := shared.MakeDotTinzenite(t.Path)
	if err != nil {
		return err
	}
	// write all peers to files
	for _, peer := range t.allPeers {
		err := peer.Store(t.Path)
		if err != nil {
			return err
		}
	}
	// store local peer info with toxdata
	toxData, err := t.channel.ToxData()
	if err != nil {
		return err
	}
	toxPeerDump := &shared.ToxPeerDump{
		SelfPeer: t.selfpeer,
		ToxData:  toxData}
	err = toxPeerDump.Store(t.Path)
	if err != nil {
		return err
	}
	// store auth file
	err = t.auth.Store(t.Path)
	if err != nil {
		return err
	}
	// update model for tinzenite dir to catch above stores
	err = t.model.PartialUpdate(t.Path + "/" + shared.TINZENITEDIR)
	if err != nil {
		return err
	}
	// finally store model (last because peers and org stuff is included!)
	return t.model.Store()
}

/*
PrintStatus returns a formatted string of the peer status.
*/
func (t *Tinzenite) PrintStatus() string {
	var out string
	out += "Online:\n"
	addresses, err := t.channel.FriendAddresses()
	if err != nil {
		out += "channel.FriendAddresses failed!"
	} else {
		var count int
		for _, address := range addresses {
			online, err := t.channel.IsAddressOnline(address)
			var insert string
			if err != nil {
				insert = "ERROR"
			} else {
				insert = fmt.Sprintf("%v", online)
			}
			out += address[:16] + " :: " + insert + "\n"
			count++
		}
		out += "Total friends: " + fmt.Sprintf("%d", count)
	}
	return out
}

/*
DisconnectPeer does exactly that. NOTE: this is a passive action and doesn't do
anything except remove the peer from the network. The peer is not further
notified.

TODO: maybe not use name but Identification?
*/
func (t *Tinzenite) DisconnectPeer(peerName string) {
	var newPeers []*shared.Peer
	for _, peer := range t.allPeers {
		if t.selfpeer.Identification == peer.Identification {
			continue
		}
		if peer.Name == peerName {
			log.Println("Removing", peer.Name, "at", peer.Address[:8])
			// delete peer file
			path := shared.CreatePath(t.Path, shared.TINZENITEDIR+"/"+shared.ORGDIR+"/"+shared.PEERSDIR+"/"+peer.Identification+shared.ENDING)
			err := t.model.ApplyRemove(path, nil)
			if err != nil {
				log.Println("DisconnectPeer:", err)
			}
			// remove from channel
			err = t.channel.RemoveConnection(peer.Address)
			if err != nil {
				log.Println("DisconnectPeer:", err)
			}
			// write peer to all removals so that no removals will be orphaned
			removePath := t.Path + "/" + shared.TINZENITEDIR + "/" + shared.REMOVEDIR
			allRemovals, _ := ioutil.ReadDir(removePath)
			// for every object that is currently being removed
			for _, stat := range allRemovals {
				// write the to be removed peer as done
				err := t.model.UpdateRemovalDir(stat.Name(), peer.Identification)
				if err != nil {
					// warn if it failed
					log.Println("Tinzenite: failed to purge removed peer from removal!")
				}
			}
			// continue does not readd to tinzenite, removing the reference to it
			continue
		}
		newPeers = append(newPeers, peer)
	}
	t.allPeers = newPeers
}

/*
applyPeers loads all peers and readies the communication channel accordingly.
*/
func (t *Tinzenite) applyPeers() error {
	peers, err := shared.LoadPeers(t.Path)
	if err != nil {
		return err
	}
	// make sure they are all tox ready
	for _, peer := range peers {
		// tox will return an error if the address has already been added, so we just ignore it
		_ = t.channel.AcceptConnection(peer.Address)
	}
	// finally apply
	t.allPeers = peers
	return nil
}

/*
Send the given message to the peer with the address if online.
*/
func (t *Tinzenite) send(address, msg string) error {
	return t.channel.Send(address, msg)
}

/*
Merge an update message to the local model.
*/
func (t *Tinzenite) merge(msg *shared.UpdateMessage) error {
	relPath := shared.CreatePath(t.Path, msg.Object.Path)
	// first: apply local changes to model (this is why writing PartialUpdate was no waste of time, isn't this cool?! :D)
	err := t.model.PartialUpdate(relPath.FullPath())
	if err != nil {
		return err
	}
	// check if content is same, no need for merge then (except for version)
	stin, err := t.model.GetInfo(relPath)
	if err != nil {
		log.Println("Merge: can not check if content is same!")
	} else {
		if stin.Content == msg.Object.Content {
			// log.Println("Core:", "Merge not required as updates are in sync!")
			// so all we need to do is apply the version update
			return t.model.ApplyModify(relPath, &msg.Object)
		}
	}
	// second: move to new name
	err = os.Rename(relPath.FullPath(), relPath.FullPath()+LOCAL)
	if err != nil {
		log.Println("Merge: original can not be found!")
		return err
	}
	// third: apply create of local version
	localVersionPath := relPath.RenameLastElement(relPath.LastElement() + LOCAL)
	err = t.model.ApplyCreate(localVersionPath, nil)
	if err != nil {
		log.Println("Merge: creating local merge file failed!")
		return err
	}
	// fourth: remove original
	err = t.model.ApplyRemove(relPath, nil)
	if err != nil {
		log.Println("Merge: removing original failed!")
		return err
	}
	// fifth: change path and apply remote as create
	msg.Operation = shared.OpCreate
	msg.Object.Path = relPath.SubPath() + REMOTE
	msg.Object.Name = relPath.LastElement() + REMOTE
	oldID := msg.Object.Identification
	msg.Object.Identification, err = shared.NewIdentifier()
	if err != nil {
		log.Println("Merge: failed to create new identifier!")
		return err
	}
	// new id --> rename temp file
	tempPath := t.Path + "/" + shared.TINZENITEDIR + "/" + shared.TEMPDIR
	err = os.Rename(tempPath+"/"+oldID, tempPath+"/"+msg.Object.Identification)
	if err != nil {
		log.Println("Merge: ipdating remote object file failed!")
		return err
	}
	// sixth: create remote file
	err = t.model.ApplyCreate(relPath.Apply(relPath.FullPath()+REMOTE), &msg.Object)
	if err != nil {
		log.Println("Merge: creating remote merge file failed!")
		return err
	}
	return nil
}

/*
initialize the background process.
*/
func (t *Tinzenite) initialize() {
	// prepare send channel that will distribute updates
	t.wg.Add(1)
	t.stop = make(chan bool, 1)
	t.sendChannel = make(chan shared.UpdateMessage, 1)
	go t.background()
	t.model.Register(t.sendChannel)
}

/*
background function that handles all async stuff that needs to be done.
*/
func (t *Tinzenite) background() {
	defer func() { log.Println("Tinzenite: Background process stopped.") }()
	for {
		select {
		case <-t.stop:
			t.wg.Done()
			return
		case msg := <-t.sendChannel:
			// if muted don't send updates
			if t.muteFlag {
				continue
			}
			online, err := t.channel.OnlineAddresses()
			if err != nil {
				log.Println("Background:", err)
			}
			for _, address := range online {
				name := msg.Object.Name
				// for better visibility add special mark to signify directory
				if msg.Object.Directory {
					name += "/++"
				}
				log.Printf("Tin: sending <%s> of <.../%s> to %s.\n", msg.Operation, name, address[:8])
				t.channel.Send(address, msg.JSON())
			}
		} // select
	} // for
}
<|endoftext|>


<commit_before>package telegram

import (
	"errors"
	"os"

	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

const (
	tokenKey    = "BOTKEY"
	tgAPIDomain = "https://scnace.me:443/"
)

var (
	errEmptyArgs = errors.New("plz input args")
	token        string
)

func init() {
	token = os.Getenv(tokenKey)
}

func connectTG() (bot *tgbotapi.BotAPI, err error) {
	return tgbotapi.NewBotAPI(token)
}
<commit_msg>modify domain<commit_after>package telegram

import (
	"errors"
	"os"

	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)

const (
	tokenKey    = "BOTKEY"
	tgAPIDomain = "https://api.scnace.me:443/"
)

var (
	errEmptyArgs = errors.New("plz input args")
	token        string
)

func init() {
	token = os.Getenv(tokenKey)
}

func connectTG() (bot *tgbotapi.BotAPI, err error) {
	return tgbotapi.NewBotAPI(token)
}
<|endoftext|>


<commit_before>package telegram

import (
	"net/http"

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Request interface {
	doWith(c *Client) (result interface{}, err error)
}

type Error struct {
	Description string
}

func (err Error) Error() string {
	return err.Description
}

type SendMessageRequest struct {
	ChatID           int64
	Text             string
	ParseMode        string
	ReplyToMessageID int
	ReplyMarkup      ReplyMarkup
}

func (r SendMessageRequest) config() tgbotapi.MessageConfig {
	message := tgbotapi.NewMessage(r.ChatID, r.Text)
	if r.ParseMode != "" {
		message.ParseMode = r.ParseMode
	}
	if r.ReplyToMessageID != 0 {
		message.ReplyToMessageID = r.ReplyToMessageID
	}
	if r.ReplyMarkup != nil {
		message.ReplyMarkup = r.ReplyMarkup.markup()
	}
	return message
}

func (r SendMessageRequest) doWith(c *Client) (result interface{}, err error) {
	m, err := c.client.Send(r.config())
	if err != nil {
		return nil, Error{Description: err.(tgbotapi.Error).Message}
	}
	return m, nil
}

type EditMessageTextRequest struct {
	ChatID          int64
	MessageID       int
	InlineMessageID string
	Text            string
	ParseMode       string
	ReplyMarkup     InlineKeyboardMarkup
}

func (r EditMessageTextRequest) config() (config tgbotapi.EditMessageTextConfig) {
	if r.InlineMessageID != "" {
		config = tgbotapi.EditMessageTextConfig{
			BaseEdit: tgbotapi.BaseEdit{
				InlineMessageID: r.InlineMessageID,
			},
			Text: r.Text,
		}
	} else {
		config = tgbotapi.NewEditMessageText(r.ChatID, r.MessageID, r.Text)
	}
	if r.ParseMode != "" {
		config.ParseMode = r.ParseMode
	}
	if len(r.ReplyMarkup.InlineKeyboard) > 0 {
		markup := r.ReplyMarkup.inlineKeyboardMarkup()
		config.ReplyMarkup = &markup
	}
	return
}

func (r EditMessageTextRequest) doWith(c *Client) (result interface{}, err error) {
	m, err := c.client.Send(r.config())
	if err != nil {
		return nil, Error{Description: err.(tgbotapi.Error).Message}
	}
	return m, nil
}

type AnswerCallbackQueryRequest struct {
	CallbackQueryID string
	Text            string
}

func (r AnswerCallbackQueryRequest) doWith(c *Client) (result interface{}, err error) {
	config := tgbotapi.NewCallback(r.CallbackQueryID, r.Text)
	res, err := c.client.AnswerCallbackQuery(config)
	if err != nil {
		return nil, Error{Description: res.Description}
	}
	return
}

type AnswerInlineQueryRequest struct {
}

type Client struct {
	client *tgbotapi.BotAPI
}

func (c *Client) Do(request Request) error {
	_, err := request.doWith(c)
	return err
}

func NewClient(token string, httpClient *http.Client) (*Client, error) {
	client, err := tgbotapi.NewBotAPIWithClient(token, httpClient)
	if err != nil {
		return nil, err
	}
	return &Client{
		client: client,
	}, nil
}
<commit_msg>telegram: Create tgbotapi struct directly<commit_after>package telegram

import (
	"net/http"

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Request interface {
	doWith(c *Client) (result interface{}, err error)
}

type Error struct {
	Description string
}

func (err Error) Error() string {
	return err.Description
}

type SendMessageRequest struct {
	ChatID           int64
	Text             string
	ParseMode        string
	ReplyToMessageID int
	ReplyMarkup      ReplyMarkup
}

func (r SendMessageRequest) config() tgbotapi.MessageConfig {
	message := tgbotapi.NewMessage(r.ChatID, r.Text)
	if r.ParseMode != "" {
		message.ParseMode = r.ParseMode
	}
	if r.ReplyToMessageID != 0 {
		message.ReplyToMessageID = r.ReplyToMessageID
	}
	if r.ReplyMarkup != nil {
		message.ReplyMarkup = r.ReplyMarkup.markup()
	}
	return message
}

func (r SendMessageRequest) doWith(c *Client) (result interface{}, err error) {
	m, err := c.client.Send(r.config())
	if err != nil {
		return nil, Error{Description: err.(tgbotapi.Error).Message}
	}
	return m, nil
}

type EditMessageTextRequest struct {
	ChatID          int64
	MessageID       int
	InlineMessageID string
	Text            string
	ParseMode       string
	ReplyMarkup     InlineKeyboardMarkup
}

func (r EditMessageTextRequest) config() (config tgbotapi.EditMessageTextConfig) {
	if r.InlineMessageID != "" {
		config = tgbotapi.EditMessageTextConfig{
			BaseEdit: tgbotapi.BaseEdit{
				InlineMessageID: r.InlineMessageID,
			},
			Text: r.Text,
		}
	} else {
		config = tgbotapi.NewEditMessageText(r.ChatID, r.MessageID, r.Text)
	}
	if r.ParseMode != "" {
		config.ParseMode = r.ParseMode
	}
	if len(r.ReplyMarkup.InlineKeyboard) > 0 {
		markup := r.ReplyMarkup.inlineKeyboardMarkup()
		config.ReplyMarkup = &markup
	}
	return
}

func (r EditMessageTextRequest) doWith(c *Client) (result interface{}, err error) {
	m, err := c.client.Send(r.config())
	if err != nil {
		return nil, Error{Description: err.(tgbotapi.Error).Message}
	}
	return m, nil
}

type AnswerCallbackQueryRequest struct {
	CallbackQueryID string
	Text            string
}

func (r AnswerCallbackQueryRequest) doWith(c *Client) (result interface{}, err error) {
	config := tgbotapi.NewCallback(r.CallbackQueryID, r.Text)
	res, err := c.client.AnswerCallbackQuery(config)
	if err != nil {
		return nil, Error{Description: res.Description}
	}
	return
}

type AnswerInlineQueryRequest struct {
}

type Client struct {
	client *tgbotapi.BotAPI
}

func (c *Client) Do(request Request) error {
	_, err := request.doWith(c)
	return err
}

func NewClient(token string, httpClient *http.Client) (*Client, error) {
	client := &tgbotapi.BotAPI{
		Token:  token,
		Client: httpClient,
	}
	return &Client{
		client: client,
	}, nil
}
<|endoftext|>


<commit_before>/*
http://www.apache.org/licenses/LICENSE-2.0.txt

Copyright 2015 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package postgresql

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"database/sql"

	"github.com/intelsdi-x/snap/control/plugin"
	"github.com/intelsdi-x/snap/control/plugin/cpolicy"
	"github.com/intelsdi-x/snap/core/ctypes"
	// Import of postgresql library
	_ "github.com/lib/pq"
)

const (
	name         = "postgresql"
	version      = 5
	pluginType   = plugin.PublisherPluginType
	tableColumns = "(id SERIAL PRIMARY KEY, time_posted timestamp with time zone, key_column VARCHAR(200), value_column VARCHAR(200))"
	timeFormat   = time.RFC3339
)

// PostgreSQLPublisher struct
type PostgreSQLPublisher struct {
}

// NewPostgreSQLPublisher return new PostgreSQL instance
func NewPostgreSQLPublisher() *PostgreSQLPublisher {
	return &PostgreSQLPublisher{}
}

// Publish sends data to PostgreSQL server
func (s *PostgreSQLPublisher) Publish(contentType string, content []byte, config map[string]ctypes.ConfigValue) error {
	logger := log.New()
	logger.Println("Publishing started")
	var metrics []plugin.PluginMetricType

	switch contentType {
	case plugin.SnapGOBContentType:
		dec := gob.NewDecoder(bytes.NewBuffer(content))
		if err := dec.Decode(&metrics); err != nil {
			logger.Printf("Error decoding: error=%v content=%v", err, content)
			return err
		}
	default:
		logger.Printf("Error unknown content type '%v'", contentType)
		return fmt.Errorf("Unknown content type '%s'", contentType)
	}

	logger.Printf("publishing %v to %v", metrics, config)

	tableName := config["table_name"].(ctypes.ConfigValueStr).Value

	// Open connection and ping to make sure it works
	db, err := getPostgreSQLConn(config)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	defer db.Close()

	nowTime := time.Now().Format(timeFormat)
	var key, value string
	for _, m := range metrics {
		key = sliceToNamespace(m.Namespace())
		value, err = interfaceToString(m.Data())
		if err == nil {
			query := fmt.Sprintf("INSERT INTO %s (id, time_posted, key_column, value_column) VALUES (DEFAULT, '%s', '%s', '%s')", tableName, nowTime, key, value)
			_, err := db.Exec(query)
			if err != nil {
				errMsg := fmt.Sprintf("pq: relation \"%s\" does not exist", tableName)
				if err.Error() == errMsg {
					_, err = createTable(db, tableName)
					if err != nil {
						logger.Printf("Error: %v", err)
						return err
					}

				}
				logger.Printf("Error: %v", err)
				return err
			}
		} else {
			logger.Printf("Error: %v", err)
			return err
		}
	}
	return nil
}

// Meta returns plugin meta data info
func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(name, version, pluginType, []string{plugin.SnapGOBContentType}, []string{plugin.SnapGOBContentType})
}

func getPostgreSQLConn(config map[string]ctypes.ConfigValue) (*sql.DB, error) {
	logger := log.New()
	hostName := config["hostname"].(ctypes.ConfigValueStr).Value
	port := config["port"].(ctypes.ConfigValueInt).Value
	username := config["username"].(ctypes.ConfigValueStr).Value
	password := config["password"].(ctypes.ConfigValueStr).Value
	database := config["database"].(ctypes.ConfigValueStr).Value
	conn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", hostName, port, username, password, database)
	db, err := sql.Open("postgres", conn)
	if err != nil {
		logger.Printf("Error: %v", err)
		return db, err
	}
	err = db.Ping()
	if err != nil {
		logger.Printf("Error: %v", err)
		return db, err
	}
	return db, err
}

func createTable(db *sql.DB, tableName string) (bool, error) {
	logger := log.New()
	query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s %s", tableName, tableColumns)
	_, err := db.Exec(query)
	if err != nil {
		logger.Printf("Error: %v", err)
		return false, err
	}
	query = fmt.Sprintf("CREATE INDEX key_index on %s (key_column)", tableName)
	_, err = db.Exec(query)
	if err != nil {
		logger.Printf("Error: %v", err)
		return false, err
	}
	return true, err
}

// GetConfigPolicy returns a config policy
func (s *PostgreSQLPublisher) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
	cp := cpolicy.New()
	config := cpolicy.NewPolicyNode()

	username, err := cpolicy.NewStringRule("username", true)
	handleErr(err)
	username.Description = "Username to login to the PostgreSQL server"

	password, err := cpolicy.NewStringRule("password", true)
	handleErr(err)
	password.Description = "Password to login to the PostgreSQL server"

	database, err := cpolicy.NewStringRule("database", true)
	handleErr(err)
	database.Description = "The postgresql database that data will be pushed to"

	tableName, err := cpolicy.NewStringRule("table_name", true)
	handleErr(err)
	tableName.Description = "The postgresql table within the database where information will be stored"

	hostName, err := cpolicy.NewStringRule("hostname", true, "localhost")
	handleErr(err)
	tableName.Description = "The postgresql server ip or domain name"

	port, err := cpolicy.NewIntegerRule("port", true, 5432)
	handleErr(err)
	port.Description = "The postgresql server port number"

	config.Add(username, password, database, tableName, hostName, port)

	cp.Add([]string{""}, config)
	return cp, nil

}

func handleErr(e error) {
	if e != nil {
		panic(e)
	}
}

func sliceToString(slice []string) string {
	return strings.Join(slice, ", ")
}

func sliceToNamespace(slice []string) string {
	return strings.Join(slice, ".")
}

// Supported types: []string, []int, int, float64, string
func interfaceToString(face interface{}) (string, error) {
	var (
		ret string
		err error
	)
	switch val := face.(type) {
	case []string:
		ret = sliceToString(val)
	case bool:
		if val == true {
			ret = "1"
		} else {
			ret = "0"
		}
	case []int:
		length := len(val)
		if length == 0 {
			return ret, err
		}
		ret = strconv.Itoa(val[0])
		if length == 1 {
			return ret, err
		}
		for i := 1; i < length; i++ {
			ret += ", "
			ret += strconv.Itoa(val[i])
		}
	case int:
		ret = strconv.Itoa(val)
	case float64:
		ret = strconv.FormatFloat(val, 'g', -1, 64)
	case string:
		ret = val
	default:
		err = errors.New("unsupported type")
	}
	return ret, err
}
<commit_msg>Update version 6 with snap dep change to v0.11.0-beta<commit_after>/*
http://www.apache.org/licenses/LICENSE-2.0.txt

Copyright 2015 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package postgresql

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"database/sql"

	"github.com/intelsdi-x/snap/control/plugin"
	"github.com/intelsdi-x/snap/control/plugin/cpolicy"
	"github.com/intelsdi-x/snap/core/ctypes"
	// Import of postgresql library
	_ "github.com/lib/pq"
)

const (
	name         = "postgresql"
	version      = 6
	pluginType   = plugin.PublisherPluginType
	tableColumns = "(id SERIAL PRIMARY KEY, time_posted timestamp with time zone, key_column VARCHAR(200), value_column VARCHAR(200))"
	timeFormat   = time.RFC3339
)

// PostgreSQLPublisher struct
type PostgreSQLPublisher struct {
}

// NewPostgreSQLPublisher return new PostgreSQL instance
func NewPostgreSQLPublisher() *PostgreSQLPublisher {
	return &PostgreSQLPublisher{}
}

// Publish sends data to PostgreSQL server
func (s *PostgreSQLPublisher) Publish(contentType string, content []byte, config map[string]ctypes.ConfigValue) error {
	logger := log.New()
	logger.Println("Publishing started")
	var metrics []plugin.PluginMetricType

	switch contentType {
	case plugin.SnapGOBContentType:
		dec := gob.NewDecoder(bytes.NewBuffer(content))
		if err := dec.Decode(&metrics); err != nil {
			logger.Printf("Error decoding: error=%v content=%v", err, content)
			return err
		}
	default:
		logger.Printf("Error unknown content type '%v'", contentType)
		return fmt.Errorf("Unknown content type '%s'", contentType)
	}

	logger.Printf("publishing %v to %v", metrics, config)

	tableName := config["table_name"].(ctypes.ConfigValueStr).Value

	// Open connection and ping to make sure it works
	db, err := getPostgreSQLConn(config)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	defer db.Close()

	nowTime := time.Now().Format(timeFormat)
	var key, value string
	for _, m := range metrics {
		key = sliceToNamespace(m.Namespace())
		value, err = interfaceToString(m.Data())
		if err == nil {
			query := fmt.Sprintf("INSERT INTO %s (id, time_posted, key_column, value_column) VALUES (DEFAULT, '%s', '%s', '%s')", tableName, nowTime, key, value)
			_, err := db.Exec(query)
			if err != nil {
				errMsg := fmt.Sprintf("pq: relation \"%s\" does not exist", tableName)
				if err.Error() == errMsg {
					_, err = createTable(db, tableName)
					if err != nil {
						logger.Printf("Error: %v", err)
						return err
					}

				}
				logger.Printf("Error: %v", err)
				return err
			}
		} else {
			logger.Printf("Error: %v", err)
			return err
		}
	}
	return nil
}

// Meta returns plugin meta data info
func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(name, version, pluginType, []string{plugin.SnapGOBContentType}, []string{plugin.SnapGOBContentType})
}

func getPostgreSQLConn(config map[string]ctypes.ConfigValue) (*sql.DB, error) {
	logger := log.New()
	hostName := config["hostname"].(ctypes.ConfigValueStr).Value
	port := config["port"].(ctypes.ConfigValueInt).Value
	username := config["username"].(ctypes.ConfigValueStr).Value
	password := config["password"].(ctypes.ConfigValueStr).Value
	database := config["database"].(ctypes.ConfigValueStr).Value
	conn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", hostName, port, username, password, database)
	db, err := sql.Open("postgres", conn)
	if err != nil {
		logger.Printf("Error: %v", err)
		return db, err
	}
	err = db.Ping()
	if err != nil {
		logger.Printf("Error: %v", err)
		return db, err
	}
	return db, err
}

func createTable(db *sql.DB, tableName string) (bool, error) {
	logger := log.New()
	query := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s %s", tableName, tableColumns)
	_, err := db.Exec(query)
	if err != nil {
		logger.Printf("Error: %v", err)
		return false, err
	}
	query = fmt.Sprintf("CREATE INDEX key_index on %s (key_column)", tableName)
	_, err = db.Exec(query)
	if err != nil {
		logger.Printf("Error: %v", err)
		return false, err
	}
	return true, err
}

// GetConfigPolicy returns a config policy
func (s *PostgreSQLPublisher) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
	cp := cpolicy.New()
	config := cpolicy.NewPolicyNode()

	username, err := cpolicy.NewStringRule("username", true)
	handleErr(err)
	username.Description = "Username to login to the PostgreSQL server"

	password, err := cpolicy.NewStringRule("password", true)
	handleErr(err)
	password.Description = "Password to login to the PostgreSQL server"

	database, err := cpolicy.NewStringRule("database", true)
	handleErr(err)
	database.Description = "The postgresql database that data will be pushed to"

	tableName, err := cpolicy.NewStringRule("table_name", true)
	handleErr(err)
	tableName.Description = "The postgresql table within the database where information will be stored"

	hostName, err := cpolicy.NewStringRule("hostname", true, "localhost")
	handleErr(err)
	tableName.Description = "The postgresql server ip or domain name"

	port, err := cpolicy.NewIntegerRule("port", true, 5432)
	handleErr(err)
	port.Description = "The postgresql server port number"

	config.Add(username, password, database, tableName, hostName, port)

	cp.Add([]string{""}, config)
	return cp, nil

}

func handleErr(e error) {
	if e != nil {
		panic(e)
	}
}

func sliceToString(slice []string) string {
	return strings.Join(slice, ", ")
}

func sliceToNamespace(slice []string) string {
	return strings.Join(slice, ".")
}

// Supported types: []string, []int, int, float64, string
func interfaceToString(face interface{}) (string, error) {
	var (
		ret string
		err error
	)
	switch val := face.(type) {
	case []string:
		ret = sliceToString(val)
	case bool:
		if val == true {
			ret = "1"
		} else {
			ret = "0"
		}
	case []int:
		length := len(val)
		if length == 0 {
			return ret, err
		}
		ret = strconv.Itoa(val[0])
		if length == 1 {
			return ret, err
		}
		for i := 1; i < length; i++ {
			ret += ", "
			ret += strconv.Itoa(val[i])
		}
	case int:
		ret = strconv.Itoa(val)
	case float64:
		ret = strconv.FormatFloat(val, 'g', -1, 64)
	case string:
		ret = val
	default:
		err = errors.New("unsupported type")
	}
	return ret, err
}
<|endoftext|>


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

package stomp

import (
	"os"
	"strconv"
	"strings"
	"time"
)

// Network reader
func (c *Connection) reader() {
	//
	q := false
	gf := func() {
		q = <-c.rsd
	}
	go gf()

	for {
		f, e := c.readFrame()
		if e != nil {
			if e == os.EOF {
				break
			}
			c.input <- MessageData{Message{"", Headers{}, NULLBUFF}, e}
			break
		}

		if f.Command == "" && q {
			break
		}

		d := MessageData{Message{f.Command, f.Headers, f.Body}, e}
		c.input <- d
		if q {
			break
		}

	}

}

// Frame reader
func (c *Connection) readFrame() (f Frame, e os.Error) {
	f = Frame{"", Headers{}, NULLBUFF}
	e = nil
	// Read f.Command or line ends (maybe heartbeats)
	for {
		s, e := c.rdr.ReadString('\n')
		if s == "" {
			return f, e
		}
		if e != nil {
			return f, e
		}
		if c.hbd != nil {
			c.hbd.lr = time.Nanoseconds() // Latest good read
		}
		f.Command = s[0 : len(s)-1]
		if s != "\n" {
			break
		}
	}
	// Read f.Headers
	for {
		s, e := c.rdr.ReadString('\n')
		if e != nil {
			return f, e
		}
		if c.hbd != nil {
			c.hbd.lr = time.Nanoseconds() // Latest good read
		}
		if s == "\n" {
			break
		}
		//
		p := strings.SplitN(s, ":", 2)
		k := p[0]
		v := p[1]
		if c.protocol != SPL_10 {
			k = decode(k)
			v = decode(v)
		}
		f.Headers = append(f.Headers, k, v)
	}
	// Read f.Body
	if v, ok := f.Headers.Contains("content-length"); ok {
		l, e := strconv.Atoi(strings.TrimSpace(v))
		if e != nil {
			return f, e
		}
		if l == 0 {
			f.Body, e = readUntilNul(c.rdr)
		} else {
			f.Body, e = readBody(c.rdr, l)
		}
	} else {
		// content-length not present
		f.Body, e = readUntilNul(c.rdr)
	}
	if e != nil {
		return f, e
	}
	if c.hbd != nil {
		c.hbd.lr = time.Nanoseconds() // Latest good read
	}
	//
	return f, e
}
<commit_msg>Do not include trailing LF while reading header values.<commit_after>//
// Copyright © 2011 Guy M. Allard
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package stomp

import (
	"os"
	"strconv"
	"strings"
	"time"
)

// Network reader
func (c *Connection) reader() {
	//
	q := false
	gf := func() {
		q = <-c.rsd
	}
	go gf()

	for {
		f, e := c.readFrame()
		if e != nil {
			if e == os.EOF {
				break
			}
			c.input <- MessageData{Message{"", Headers{}, NULLBUFF}, e}
			break
		}

		if f.Command == "" && q {
			break
		}

		d := MessageData{Message{f.Command, f.Headers, f.Body}, e}
		c.input <- d
		if q {
			break
		}

	}

}

// Frame reader
func (c *Connection) readFrame() (f Frame, e os.Error) {
	f = Frame{"", Headers{}, NULLBUFF}
	e = nil
	// Read f.Command or line ends (maybe heartbeats)
	for {
		s, e := c.rdr.ReadString('\n')
		if s == "" {
			return f, e
		}
		if e != nil {
			return f, e
		}
		if c.hbd != nil {
			c.hbd.lr = time.Nanoseconds() // Latest good read
		}
		f.Command = s[0 : len(s)-1]
		if s != "\n" {
			break
		}
	}
	// Read f.Headers
	for {
		s, e := c.rdr.ReadString('\n')
		if e != nil {
			return f, e
		}
		if c.hbd != nil {
			c.hbd.lr = time.Nanoseconds() // Latest good read
		}
		if s == "\n" {
			break
		}
		//
		s = s[0:len(s)-1]
		p := strings.SplitN(s, ":", 2)
		k := p[0]
		v := p[1]
		if c.protocol != SPL_10 {
			k = decode(k)
			v = decode(v)
		}
		f.Headers = append(f.Headers, k, v)
	}
	// Read f.Body
	if v, ok := f.Headers.Contains("content-length"); ok {
		l, e := strconv.Atoi(strings.TrimSpace(v))
		if e != nil {
			return f, e
		}
		if l == 0 {
			f.Body, e = readUntilNul(c.rdr)
		} else {
			f.Body, e = readBody(c.rdr, l)
		}
	} else {
		// content-length not present
		f.Body, e = readUntilNul(c.rdr)
	}
	if e != nil {
		return f, e
	}
	if c.hbd != nil {
		c.hbd.lr = time.Nanoseconds() // Latest good read
	}
	//
	return f, e
}
<|endoftext|>


<commit_before>package sercom

import (
	"fmt"
	"os"

	"go9p.googlecode.com/hg/p"
	"go9p.googlecode.com/hg/p/srv"
	"github.com/knieriem/g/go9p"
)

var (
	Debug    bool
	Debugall bool
)

type ctl struct {
	srv.File
	dev Port
}
type data struct {
	srv.File
	dev     Port
	clunked bool
	tmp     []byte
	ch      chan int
}

func (c *ctl) Write(fid *srv.FFid, buf []byte, offset uint64) (int, *p.Error) {
	err := c.dev.Ctl(string(buf))
	return len(buf), go9p.ToError(err)
}

func (d *data) Read(fid *srv.FFid, buf []byte, offset uint64) (n int, e9 *p.Error) {
	var err os.Error

	d.lock()
	if nt := len(d.tmp); nt != 0 {
		n = len(buf)
		if n > nt {
			n = nt
		}
		copy(buf, d.tmp[:n])
		d.tmp = d.tmp[n:]
	} else {
		n, err = d.dev.Read(buf)
		if d.clunked {
			d.tmp = make([]byte, n)
			copy(d.tmp, buf[:n])
			d.clunked = false
			n = 0
		}
	}
	d.unlock()
	e9 = go9p.ToError(err)
	return
}
func (d *data) Write(fid *srv.FFid, buf []byte, offset uint64) (int, *p.Error) {
	n, err := d.dev.Write(buf)
	return n, go9p.ToError(err)
}
func (d *data) Clunk(*srv.FFid) *p.Error {
	d.clunked = true
	return nil
}

func (d *data) lock() {
	d.ch <- 1
}
func (d *data) unlock() {
	<-d.ch
}

// Serve a previously opened serial device via 9P.
// `addr' shoud be of form "host:port", where host
// may be missing.
func Serve9P(addr string, dev Port) os.Error {
	user := go9p.CurrentUser()
	root := new(srv.File)
	err := root.Add(nil, "/", user, nil, p.DMDIR|0555, nil)
	if err != nil {
		goto error
	}

	c := new(ctl)
	c.dev = dev
	err = c.Add(root, "ctl", user, nil, 0664, c)
	if err != nil {
		goto error
	}
	d := new(data)
	d.dev = dev
	d.ch = make(chan int, 1)
	err = d.Add(root, "data", user, nil, 0664, d)
	if err != nil {
		goto error
	}

	s := srv.NewFileSrv(root)
	s.Dotu = true

	switch {
	case Debugall:
		s.Debuglevel = 2
	case Debug:
		s.Debuglevel = 1
	}

	s.Start(s)
	err = s.StartNetListener("tcp", addr)
	if err != nil {
		goto error
	}

	return nil

error:
	return os.NewError(fmt.Sprintf("Error: %s %d", err.Error, err.Errornum))
}
<commit_msg>sercom/srv: use go9p/srv.File's Mutex, instead of an own locking mechanism<commit_after>package sercom

import (
	"fmt"
	"os"

	"go9p.googlecode.com/hg/p"
	"go9p.googlecode.com/hg/p/srv"
	"github.com/knieriem/g/go9p"
)

var (
	Debug    bool
	Debugall bool
)

type ctl struct {
	srv.File
	dev Port
}
type data struct {
	srv.File
	dev     Port
	clunked bool
	tmp     []byte
}

func (c *ctl) Write(fid *srv.FFid, buf []byte, offset uint64) (int, *p.Error) {
	err := c.dev.Ctl(string(buf))
	return len(buf), go9p.ToError(err)
}

func (d *data) Read(fid *srv.FFid, buf []byte, offset uint64) (n int, e9 *p.Error) {
	var err os.Error

	d.Lock()
	defer d.Unlock()

	if nt := len(d.tmp); nt != 0 {
		n = len(buf)
		if n > nt {
			n = nt
		}
		copy(buf, d.tmp[:n])
		d.tmp = d.tmp[n:]
	} else {
		n, err = d.dev.Read(buf)
		if d.clunked {
			d.tmp = make([]byte, n)
			copy(d.tmp, buf[:n])
			d.clunked = false
			n = 0
		}
	}
	e9 = go9p.ToError(err)
	return
}
func (d *data) Write(fid *srv.FFid, buf []byte, offset uint64) (int, *p.Error) {
	n, err := d.dev.Write(buf)
	return n, go9p.ToError(err)
}
func (d *data) Clunk(*srv.FFid) *p.Error {
	d.clunked = true
	return nil
}

// Serve a previously opened serial device via 9P.
// `addr' shoud be of form "host:port", where host
// may be missing.
func Serve9P(addr string, dev Port) os.Error {
	user := go9p.CurrentUser()
	root := new(srv.File)
	err := root.Add(nil, "/", user, nil, p.DMDIR|0555, nil)
	if err != nil {
		goto error
	}

	c := new(ctl)
	c.dev = dev
	err = c.Add(root, "ctl", user, nil, 0664, c)
	if err != nil {
		goto error
	}
	d := new(data)
	d.dev = dev
	err = d.Add(root, "data", user, nil, 0664, d)
	if err != nil {
		goto error
	}

	s := srv.NewFileSrv(root)
	s.Dotu = true

	switch {
	case Debugall:
		s.Debuglevel = 2
	case Debug:
		s.Debuglevel = 1
	}

	s.Start(s)
	err = s.StartNetListener("tcp", addr)
	if err != nil {
		goto error
	}

	return nil

error:
	return os.NewError(fmt.Sprintf("Error: %s %d", err.Error, err.Errornum))
}
<|endoftext|>


<commit_before>package template

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/HeavyHorst/memkv"
	"github.com/HeavyHorst/remco/backends"
	"github.com/HeavyHorst/remco/log"
	"github.com/HeavyHorst/remco/template/fileutil"
	"github.com/Sirupsen/logrus"
	"github.com/flosch/pongo2"
)

type Backend struct {
	backends.StoreClient
	Name     string
	Onetime  bool
	Watch    bool
	Prefix   string
	Interval int
	Keys     []string
	store    *memkv.Store
}

// Resource is the representation of a parsed template resource.
type Resource struct {
	backends []Backend
	funcMap  map[string]interface{}
	store    memkv.Store
	sources  []*ProcessConfig
}

var ErrEmptySrc = errors.New("empty src template")

// NewResource creates a Resource.
func NewResource(backends []Backend, sources []*ProcessConfig) (*Resource, error) {
	if len(backends) == 0 {
		return nil, errors.New("A valid StoreClient is required.")
	}

	for _, v := range sources {
		if v.Src == "" {
			return nil, ErrEmptySrc
		}
	}

	tr := &Resource{
		backends: backends,
		store:    memkv.New(),
		funcMap:  newFuncMap(),
		sources:  sources,
	}

	// initialize the inidividual backend memkv Stores
	for i := range tr.backends {
		store := memkv.New()
		tr.backends[i].store = &store

		if tr.backends[i].Interval <= 0 && tr.backends[i].Onetime == false {
			log.Warning("Interval needs to be > 0: setting interval to 60")
			tr.backends[i].Interval = 60
		}
	}

	addFuncs(tr.funcMap, tr.store.FuncMap)

	return tr, nil
}

// setVars sets the Vars for template resource.
func (t *Resource) setVars(storeClient Backend) error {
	var err error

	log.WithFields(logrus.Fields{
		"store":      storeClient.Name,
		"key_prefix": storeClient.Prefix,
	}).Debug("Retrieving keys")

	result, err := storeClient.GetValues(appendPrefix(storeClient.Prefix, storeClient.Keys))
	if err != nil {
		return err
	}

	storeClient.store.Purge()

	for key, value := range result {
		storeClient.store.Set(path.Join("/", strings.TrimPrefix(key, storeClient.Prefix)), value)
	}

	//merge all stores
	t.store.Purge()
	for _, v := range t.backends {
		for _, kv := range v.store.GetAllKVs() {
			if t.store.Exists(kv.Key) {
				log.Warning("Key collision - " + kv.Key)
			}
			t.store.Set(kv.Key, kv.Value)
		}
	}

	return nil
}

// createStageFile stages the src configuration file by processing the src
// template and setting the desired owner, group, and mode. It also sets the
// StageFile for the template resource.
// It returns an error if any.
func (t *Resource) createStageFileAndSync() error {
	for _, s := range t.sources {
		if !fileutil.IsFileExist(s.Src) {
			return errors.New("Missing template: " + s.Src)
		}

		log.WithFields(logrus.Fields{
			"template": s.Src,
		}).Debug("Compiling source template")

		tmpl, err := pongo2.FromFile(s.Src)
		if err != nil {
			return fmt.Errorf("Unable to process template %s, %s", s.Src, err)
		}

		// create TempFile in Dest directory to avoid cross-filesystem issues
		temp, err := ioutil.TempFile(filepath.Dir(s.Dst), "."+filepath.Base(s.Dst))
		if err != nil {
			return err
		}

		if err = tmpl.ExecuteWriter(t.funcMap, temp); err != nil {
			temp.Close()
			os.Remove(temp.Name())
			return err
		}

		defer temp.Close()

		// Set the owner, group, and mode on the stage file now to make it easier to
		// compare against the destination configuration file later.
		os.Chmod(temp.Name(), s.fileMode)
		os.Chown(temp.Name(), s.UID, s.GID)
		s.stageFile = temp

		if err = t.sync(s); err != nil {
			return err
		}
	}
	return nil
}

// sync compares the staged and dest config files and attempts to sync them
// if they differ. sync will run a config check command if set before
// overwriting the target config file. Finally, sync will run a reload command
// if set to have the application or service pick up the changes.
// It returns an error if any.
func (t *Resource) sync(s *ProcessConfig) error {
	staged := s.stageFile.Name()
	defer os.Remove(staged)

	log.WithFields(logrus.Fields{
		"staged": path.Base(staged),
		"dest":   s.Dst,
	}).Debug("Comparing staged and dest config files")

	ok, err := fileutil.SameFile(staged, s.Dst)
	if err != nil {
		log.Error(err.Error())
	}

	if !ok {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config out of sync")

		if s.CheckCmd != "" {
			if err := s.check(staged); err != nil {
				return errors.New("Config check failed: " + err.Error())
			}
		}
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Overwriting target config")
		err := os.Rename(staged, s.Dst)
		if err != nil {
			if strings.Contains(err.Error(), "device or resource busy") {
				log.Debug("Rename failed - target is likely a mount. Trying to write instead")
				// try to open the file and write to it
				var contents []byte
				var rerr error
				contents, rerr = ioutil.ReadFile(staged)
				if rerr != nil {
					return rerr
				}
				err := ioutil.WriteFile(s.Dst, contents, s.fileMode)
				// make sure owner and group match the temp file, in case the file was created with WriteFile
				os.Chown(s.Dst, s.UID, s.GID)
				if err != nil {
					return err
				}
			} else {
				return err
			}
		}
		if s.ReloadCmd != "" {
			if err := s.reload(); err != nil {
				return err
			}
		}

		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config has been updated")

	} else {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Target config in sync")

	}
	return nil
}

// setFileMode sets the FileMode.
func (t *Resource) setFileMode() error {
	for _, s := range t.sources {
		if err := s.setFileMode(); err != nil {
			return nil
		}
	}
	return nil
}

// Process is a convenience function that wraps calls to the three main tasks
// required to keep local configuration files in sync. First we gather vars
// from the store, then we stage a candidate configuration file, and finally sync
// things up.
// It returns an error if any.
func (t *Resource) process(storeClient Backend) error {
	if err := t.setFileMode(); err != nil {
		return err
	}
	if err := t.setVars(storeClient); err != nil {
		return err
	}
	if err := t.createStageFileAndSync(); err != nil {
		return err
	}
	return nil
}

func (t *Resource) processAll() error {
	//load all KV-pairs the first time
	var err error
	if err = t.setFileMode(); err != nil {
		return err
	}
	for _, storeClient := range t.backends {
		if err := t.setVars(storeClient); err != nil {
			return err
		}
	}
	if err = t.createStageFileAndSync(); err != nil {
		return err
	}
	return nil
}

func (s Backend) watch(stopChan chan bool, processChan chan Backend) {
	var lastIndex uint64
	keysPrefix := appendPrefix(s.Prefix, s.Keys)

	for {
		select {
		case <-stopChan:
			return
		default:
			index, err := s.WatchPrefix(s.Prefix, keysPrefix, lastIndex, stopChan)
			if err != nil {
				log.Error(err)
				// Prevent backend errors from consuming all resources.
				time.Sleep(time.Second * 2)
				continue
			}
			processChan <- s
			lastIndex = index
		}
	}
}

func (s Backend) interval(stopChan chan bool, processChan chan Backend) {
	if s.Onetime {
		return
	}
	for {
		select {
		case <-stopChan:
			return
		case <-time.After(time.Duration(s.Interval) * time.Second):
			processChan <- s
		}
	}
}

func (t *Resource) Monitor(stopChan chan bool) {
	wg := &sync.WaitGroup{}
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	done := make(chan bool)
	stopIntervalWatch := make(chan bool)
	processChan := make(chan Backend)

	defer close(processChan)

	if err := t.processAll(); err != nil {
		log.Error(err)
	}

	for _, sc := range t.backends {
		wg.Add(1)
		if sc.Watch {
			go func(s Backend) {
				defer wg.Done()
				//t.watch(s, stopChan, processChan)
				s.watch(stopIntervalWatch, processChan)
			}(sc)
		} else {
			go func(s Backend) {
				defer wg.Done()
				s.interval(stopIntervalWatch, processChan)
			}(sc)
		}
	}

	go func() {
		// If there is no goroutine left - quit
		wg.Wait()
		close(done)
	}()

	for {
		select {
		case storeClient := <-processChan:
			if err := t.process(storeClient); err != nil {
				log.Error(err)
			}
		case <-stopChan:
			close(stopIntervalWatch)
			go func() {
				for range processChan {
				}
			}()
			wg.Wait()
			return
		case <-done:
			return
		}
	}
}
<commit_msg>properly close temfile<commit_after>package template

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/HeavyHorst/memkv"
	"github.com/HeavyHorst/remco/backends"
	"github.com/HeavyHorst/remco/log"
	"github.com/HeavyHorst/remco/template/fileutil"
	"github.com/Sirupsen/logrus"
	"github.com/flosch/pongo2"
)

type Backend struct {
	backends.StoreClient
	Name     string
	Onetime  bool
	Watch    bool
	Prefix   string
	Interval int
	Keys     []string
	store    *memkv.Store
}

// Resource is the representation of a parsed template resource.
type Resource struct {
	backends []Backend
	funcMap  map[string]interface{}
	store    memkv.Store
	sources  []*ProcessConfig
}

var ErrEmptySrc = errors.New("empty src template")

// NewResource creates a Resource.
func NewResource(backends []Backend, sources []*ProcessConfig) (*Resource, error) {
	if len(backends) == 0 {
		return nil, errors.New("A valid StoreClient is required.")
	}

	for _, v := range sources {
		if v.Src == "" {
			return nil, ErrEmptySrc
		}
	}

	tr := &Resource{
		backends: backends,
		store:    memkv.New(),
		funcMap:  newFuncMap(),
		sources:  sources,
	}

	// initialize the inidividual backend memkv Stores
	for i := range tr.backends {
		store := memkv.New()
		tr.backends[i].store = &store

		if tr.backends[i].Interval <= 0 && tr.backends[i].Onetime == false {
			log.Warning("Interval needs to be > 0: setting interval to 60")
			tr.backends[i].Interval = 60
		}
	}

	addFuncs(tr.funcMap, tr.store.FuncMap)

	return tr, nil
}

// setVars sets the Vars for template resource.
func (t *Resource) setVars(storeClient Backend) error {
	var err error

	log.WithFields(logrus.Fields{
		"store":      storeClient.Name,
		"key_prefix": storeClient.Prefix,
	}).Debug("Retrieving keys")

	result, err := storeClient.GetValues(appendPrefix(storeClient.Prefix, storeClient.Keys))
	if err != nil {
		return err
	}

	storeClient.store.Purge()

	for key, value := range result {
		storeClient.store.Set(path.Join("/", strings.TrimPrefix(key, storeClient.Prefix)), value)
	}

	//merge all stores
	t.store.Purge()
	for _, v := range t.backends {
		for _, kv := range v.store.GetAllKVs() {
			if t.store.Exists(kv.Key) {
				log.Warning("Key collision - " + kv.Key)
			}
			t.store.Set(kv.Key, kv.Value)
		}
	}

	return nil
}

// createStageFile stages the src configuration file by processing the src
// template and setting the desired owner, group, and mode. It also sets the
// StageFile for the template resource.
// It returns an error if any.
func (t *Resource) createStageFileAndSync() error {
	for _, s := range t.sources {
		if !fileutil.IsFileExist(s.Src) {
			return errors.New("Missing template: " + s.Src)
		}

		log.WithFields(logrus.Fields{
			"template": s.Src,
		}).Debug("Compiling source template")

		tmpl, err := pongo2.FromFile(s.Src)
		if err != nil {
			return fmt.Errorf("Unable to process template %s, %s", s.Src, err)
		}

		// create TempFile in Dest directory to avoid cross-filesystem issues
		temp, err := ioutil.TempFile(filepath.Dir(s.Dst), "."+filepath.Base(s.Dst))
		if err != nil {
			return err
		}

		if err = tmpl.ExecuteWriter(t.funcMap, temp); err != nil {
			temp.Close()
			os.Remove(temp.Name())
			return err
		}

		// Set the owner, group, and mode on the stage file now to make it easier to
		// compare against the destination configuration file later.
		os.Chmod(temp.Name(), s.fileMode)
		os.Chown(temp.Name(), s.UID, s.GID)
		s.stageFile = temp

		temp.Close()

		if err = t.sync(s); err != nil {
			return err
		}
	}
	return nil
}

// sync compares the staged and dest config files and attempts to sync them
// if they differ. sync will run a config check command if set before
// overwriting the target config file. Finally, sync will run a reload command
// if set to have the application or service pick up the changes.
// It returns an error if any.
func (t *Resource) sync(s *ProcessConfig) error {
	staged := s.stageFile.Name()
	defer os.Remove(staged)

	log.WithFields(logrus.Fields{
		"staged": path.Base(staged),
		"dest":   s.Dst,
	}).Debug("Comparing staged and dest config files")

	ok, err := fileutil.SameFile(staged, s.Dst)
	if err != nil {
		log.Error(err.Error())
	}

	if !ok {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config out of sync")

		if s.CheckCmd != "" {
			if err := s.check(staged); err != nil {
				return errors.New("Config check failed: " + err.Error())
			}
		}
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Overwriting target config")
		err := os.Rename(staged, s.Dst)
		if err != nil {
			if strings.Contains(err.Error(), "device or resource busy") {
				log.Debug("Rename failed - target is likely a mount. Trying to write instead")
				// try to open the file and write to it
				var contents []byte
				var rerr error
				contents, rerr = ioutil.ReadFile(staged)
				if rerr != nil {
					return rerr
				}
				err := ioutil.WriteFile(s.Dst, contents, s.fileMode)
				// make sure owner and group match the temp file, in case the file was created with WriteFile
				os.Chown(s.Dst, s.UID, s.GID)
				if err != nil {
					return err
				}
			} else {
				return err
			}
		}
		if s.ReloadCmd != "" {
			if err := s.reload(); err != nil {
				return err
			}
		}

		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Info("Target config has been updated")

	} else {
		log.WithFields(logrus.Fields{
			"config": s.Dst,
		}).Debug("Target config in sync")

	}
	return nil
}

// setFileMode sets the FileMode.
func (t *Resource) setFileMode() error {
	for _, s := range t.sources {
		if err := s.setFileMode(); err != nil {
			return nil
		}
	}
	return nil
}

// Process is a convenience function that wraps calls to the three main tasks
// required to keep local configuration files in sync. First we gather vars
// from the store, then we stage a candidate configuration file, and finally sync
// things up.
// It returns an error if any.
func (t *Resource) process(storeClient Backend) error {
	if err := t.setFileMode(); err != nil {
		return err
	}
	if err := t.setVars(storeClient); err != nil {
		return err
	}
	if err := t.createStageFileAndSync(); err != nil {
		return err
	}
	return nil
}

func (t *Resource) processAll() error {
	//load all KV-pairs the first time
	var err error
	if err = t.setFileMode(); err != nil {
		return err
	}
	for _, storeClient := range t.backends {
		if err := t.setVars(storeClient); err != nil {
			return err
		}
	}
	if err = t.createStageFileAndSync(); err != nil {
		return err
	}
	return nil
}

func (s Backend) watch(stopChan chan bool, processChan chan Backend) {
	var lastIndex uint64
	keysPrefix := appendPrefix(s.Prefix, s.Keys)

	for {
		select {
		case <-stopChan:
			return
		default:
			index, err := s.WatchPrefix(s.Prefix, keysPrefix, lastIndex, stopChan)
			if err != nil {
				log.Error(err)
				// Prevent backend errors from consuming all resources.
				time.Sleep(time.Second * 2)
				continue
			}
			processChan <- s
			lastIndex = index
		}
	}
}

func (s Backend) interval(stopChan chan bool, processChan chan Backend) {
	if s.Onetime {
		return
	}
	for {
		select {
		case <-stopChan:
			return
		case <-time.After(time.Duration(s.Interval) * time.Second):
			processChan <- s
		}
	}
}

func (t *Resource) Monitor(stopChan chan bool) {
	wg := &sync.WaitGroup{}
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
	done := make(chan bool)
	stopIntervalWatch := make(chan bool)
	processChan := make(chan Backend)

	defer close(processChan)

	if err := t.processAll(); err != nil {
		log.Error(err)
	}

	for _, sc := range t.backends {
		wg.Add(1)
		if sc.Watch {
			go func(s Backend) {
				defer wg.Done()
				//t.watch(s, stopChan, processChan)
				s.watch(stopIntervalWatch, processChan)
			}(sc)
		} else {
			go func(s Backend) {
				defer wg.Done()
				s.interval(stopIntervalWatch, processChan)
			}(sc)
		}
	}

	go func() {
		// If there is no goroutine left - quit
		wg.Wait()
		close(done)
	}()

	for {
		select {
		case storeClient := <-processChan:
			if err := t.process(storeClient); err != nil {
				log.Error(err)
			}
		case <-stopChan:
			close(stopIntervalWatch)
			go func() {
				for range processChan {
				}
			}()
			wg.Wait()
			return
		case <-done:
			return
		}
	}
}
<|endoftext|>


<commit_before>package processors

import (
	"io"
	"sort"

	"github.com/dailyburn/ratchet/data"
	"github.com/dailyburn/ratchet/util"
)

// CSVWriter is handles converting data.JSON objects into CSV format,
// and writing them to the given io.Writer. The Data
// must be a valid JSON object or a slice of valid JSON objects.
// If you already have Data formatted as a CSV string you can
// use an IoWriter instead.
type CSVWriter struct {
	Writer        *util.CSVWriter
	WriteHeader   bool
	headerWritten bool
	header        []string
}

// NewCSVWriter returns a new CSVWriter wrapping the given io.Writer object
func NewCSVWriter(w io.Writer) *CSVWriter {
	return &CSVWriter{Writer: util.NewCSVWriter(w), WriteHeader: true, headerWritten: false}
}

func (w *CSVWriter) ProcessData(d data.JSON, outputChan chan data.JSON, killChan chan error) {
	// use util helper to convert Data into []map[string]interface{}
	objects, err := data.ObjectsFromJSON(d)
	util.KillPipelineIfErr(err, killChan)

	header_row := []string{}
	if w.header == nil {
		for k := range objects[0] {
			w.header = append(w.header, k)
		}
		sort.Strings(w.header)

		for _, k := range w.header {
			header_row = append(header_row, util.CSVString(k))
		}
	}

	rows := [][]string{}
	if w.WriteHeader && !w.headerWritten {
		rows = append(rows, header_row)
		w.headerWritten = true
	}

	for _, object := range objects {
		row := []string{}
		for i := range w.header {
			v := object[w.header[i]]
			row = append(row, util.CSVString(v))
		}
		rows = append(rows, row)
	}

	err = w.Writer.WriteAll(rows)
	util.KillPipelineIfErr(err, killChan)
}

func (w *CSVWriter) Finish(outputChan chan data.JSON, killChan chan error) {
}

func (w *CSVWriter) String() string {
	return "CSVWriter"
}
<commit_msg>Allow for explicit header order<commit_after>package processors

import (
	"io"
	"sort"

	"github.com/dailyburn/ratchet/data"
	"github.com/dailyburn/ratchet/util"
)

// CSVWriter is handles converting data.JSON objects into CSV format,
// and writing them to the given io.Writer. The Data
// must be a valid JSON object or a slice of valid JSON objects.
// If you already have Data formatted as a CSV string you can
// use an IoWriter instead.
type CSVWriter struct {
	Writer        *util.CSVWriter
	WriteHeader   bool
	headerWritten bool
	Header        []string
}

// NewCSVWriter returns a new CSVWriter wrapping the given io.Writer object
func NewCSVWriter(w io.Writer) *CSVWriter {
	return &CSVWriter{Writer: util.NewCSVWriter(w), WriteHeader: true, headerWritten: false}
}

func (w *CSVWriter) ProcessData(d data.JSON, outputChan chan data.JSON, killChan chan error) {
	// use util helper to convert Data into []map[string]interface{}
	objects, err := data.ObjectsFromJSON(d)
	util.KillPipelineIfErr(err, killChan)

	if w.Header == nil {
		for k := range objects[0] {
			w.Header = append(w.Header, k)
		}
		sort.Strings(w.Header)
	}

	rows := [][]string{}

	if w.WriteHeader && !w.headerWritten {
		header_row := []string{}
		for _, k := range w.Header {
			header_row = append(header_row, util.CSVString(k))
		}
		rows = append(rows, header_row)
		w.headerWritten = true
	}

	for _, object := range objects {
		row := []string{}
		for i := range w.Header {
			v := object[w.Header[i]]
			row = append(row, util.CSVString(v))
		}
		rows = append(rows, row)
	}

	err = w.Writer.WriteAll(rows)
	util.KillPipelineIfErr(err, killChan)
}

func (w *CSVWriter) Finish(outputChan chan data.JSON, killChan chan error) {
}

func (w *CSVWriter) String() string {
	return "CSVWriter"
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/mch1307/gomotics/config"
	"github.com/mch1307/gomotics/db"
	"github.com/mch1307/gomotics/log"
	"github.com/mch1307/gomotics/types"
)

const (
	// SystemInfo holds NHC startevents
	SystemInfo = "{\"cmd\":\"systeminfo\"}"
	// RegisterCMD holds NHC startevents
	RegisterCMD = "{\"cmd\":\"startevents\"}"
	// ListActions holds NHC listactions
	ListActions = "{\"cmd\":\"listactions\"}"
	// ListLocations holds NHC listlocations
	ListLocations = "{\"cmd\":\"listlocations\"}"
	// ListEnergies holds NHC listenergy
	ListEnergies = "{\"cmd\":\"listenergy\"}"
	// ListThermostats holds NHC listthermostat
	ListThermostats = "{\"cmd\":\"listthermostat\"}"
)

var (
	nhcActions   []types.Action
	nhcLocations []types.Location
	nhcEvent     []types.Event
	nhcInfo      types.NHCSystemInfo
	nhcMessage   types.Message
)

// SimpleCmd type holding a nhc command
type SimpleCmd struct {
	Cmd   string `json:"cmd"`
	ID    int    `json:"id"`
	Value int    `json:"value1"`
}

// Stringify return the string version of SimpleCmd
func (sc SimpleCmd) Stringify() string {
	tmp, _ := json.Marshal(sc)
	return string(tmp)
}

// NhcInit sends list commands to NHC in order to get all equipments
func NhcInit(cfg *config.NhcConf) {
	if len(cfg.Host) == 0 {
		log.Debug("no nhc host in conf, starting discovery")
		nhcHost := Discover()
		log.Debug("discover returned ", nhcHost)
		config.Conf.NhcConfig.Host = nhcHost.String()
		cfg.Host = config.Conf.NhcConfig.Host
	}
	if cfg.Port == 0 {
		config.Conf.NhcConfig.Port = 8000
		cfg.Port = config.Conf.NhcConfig.Port

	}
	conn, err := ConnectNhc(cfg)
	if err != nil {
		log.Fatalf("Unable to connect to NHC host: %v. Error: %v", cfg.Host, err)
	}
	reader := json.NewDecoder(conn)
	fmt.Println("Connected to NHC unit: ", cfg.Host)
	log.Info("Connected to NHC unit: ", cfg.Host)

	// sends systeminfo command to NHC
	fmt.Fprintf(conn, SystemInfo+"\n")
	if err := reader.Decode(&nhcMessage); err != nil {
		log.Fatalf("Unable to parse NHC SystemInfo message: %v", err)
	}
	Route(&nhcMessage)

	// sends listlocations command to NHC
	fmt.Fprintf(conn, ListLocations+"\n")
	if err := reader.Decode(&nhcMessage); err != nil {
		log.Fatalf("Unable to parse NHC ListLocations message: %v", err)
	}
	Route(&nhcMessage)

	// sends listActions command to NHC
	fmt.Fprintf(conn, ListActions+"\n")
	if err := reader.Decode(&nhcMessage); err != nil {
		log.Fatalf("Unable to parse NHC ListActions message: %v", err)
	}
	Route(&nhcMessage)

	defer conn.Close()
	// Build the nhc collection
	db.BuildNHCItems()
	if config.Conf.ServerConfig.LogLevel == "DEBUG" {
		db.Dump()
	}
	log.Info("Nhc init done")
}

// NhcListener start a connection to nhc host, register itself
// to receive and route all messages from nhc broadcast
func NhcListener() {
	var nhcMessage types.Message

	conn, err := ConnectNhc(&config.Conf.NhcConfig)
	if err != nil {
		log.Fatal("Fatal error connecting to NHC: ", err)
	}

	fmt.Fprintf(conn, RegisterCMD+"\n")

	for {
		reader := json.NewDecoder(conn)
		if err := reader.Decode(&nhcMessage); err != nil {
			log.Errorf("error decoding NHC message %v", err)
		}
		log.Debug(nhcMessage)
		if nhcMessage.Cmd == "startevents" {
			log.Info("listener registered")
			nhcMessage.Cmd = "dropme"
		} else {
			log.Debug("received ", &nhcMessage.Cmd)
			Route(&nhcMessage)
		}
	}
}

// Route parse and route incoming message the right handler
func Route(msg *types.Message) {
	if msg.Cmd == "listlocations" {
		/* Commented err handling as all incoming msg have already been json parsed once */
		/* 		if err := json.Unmarshal(msg.Data, &nhcLocations); err != nil {
			log.Fatal(err)
		} */
		_ = json.Unmarshal(msg.Data, &nhcLocations)
		for idx := range nhcLocations {
			db.SaveNHCLocation(nhcLocations[idx])
			//db.SaveItem(nhcLocations[idx])
		}
	} else if msg.Cmd == "listactions" {
		/* 		if err := json.Unmarshal(msg.Data, &nhcActions); err != nil {
			log.Fatal(err)
		} */
		_ = json.Unmarshal(msg.Data, &nhcActions)
		for idx := range nhcActions {
			db.SaveNHCAction(nhcActions[idx])
			//db.SaveItem(nhcActions[idx])
		}
	} else if msg.Event == "listactions" {
		/* 		if err := json.Unmarshal(msg.Data, &nhcEvent); err != nil {
			log.Errorf("unable to parse message %v, err: %v", msg.Data, err)
		} */
		msg.Event = "dropme"
		_ = json.Unmarshal(msg.Data, &nhcEvent)
		for _, rec := range nhcEvent {
			res := db.ProcessNHCEvent(rec)
			var updatedItem types.NHCItem
			_ = json.Unmarshal(res, &updatedItem)
			if config.Conf.JeedomConfig.Enabled && updatedItem.JeedomUpdState != "" {
				go UpdateJeedomState(updatedItem)
			}
			WSPool.Broadcast <- res
			//db.SaveItem(nhcEvent[idx])
		}
	} else if msg.Cmd == "systeminfo" {
		msg.Cmd = "dropme"
		_ = json.Unmarshal(msg.Data, &nhcInfo)
		db.SaveNhcSysInfo(nhcInfo)
	}
}

// Discover discover NHC controller by sending UDP pkg on port 10000
// return NHC IP address and boolean
func Discover() net.IP {
	//	var err error
	var nhcConnectString net.IP
	var targetAddr *net.UDPAddr
	data, _ := hex.DecodeString("44")
	addr := net.UDPAddr{IP: net.IPv4bcast, Port: 10000}

	conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: GetOutboundIP(), Port: 18043})
	if err != nil {
		fmt.Println("err connect: ", err)
	}
	//defer conn.Close()
	_, err = conn.WriteToUDP(data, &addr)

	b := make([]byte, 1024)
	// goroutine for reading broadcast result
	go func() {
		for {
			defer conn.Close()
			_, targetAddr, err = conn.ReadFromUDP(b)
			if err != nil {
				log.Warnf("Error: UDP read error: %v", err)
				continue
			}
			// test "nhc" connection to replying IP to make sure targetAddr is a NHC controller
			connectString := net.TCPAddr{IP: targetAddr.IP, Port: 8000}
			if err == nil {
				_, err := net.DialTCP("tcp", nil, &connectString)
				//defer nhConn.Close()
				if err == nil {
					nhcConnectString = connectString.IP
					log.Debug("return IP: ", string(nhcConnectString))
					return
				}
			}
			defer conn.Close()
			//			return
		}
	}()
	time.Sleep(time.Second * 6)
	return nhcConnectString
}

//ConnectNhc establish connection to Niko Home Control IP module
func ConnectNhc(cfg *config.NhcConf) (conn *net.TCPConn, err error) {

	connectString, err := net.ResolveTCPAddr("tcp", cfg.Host+":"+strconv.Itoa(cfg.Port))
	if err != nil {
		log.Error("connNhc ", err)
		return nil, err
	}

	conn, err = net.DialTCP("tcp", nil, connectString)
	if err != nil {
		log.Error("error connecting to nhc: ", err)
	}
	return conn, err
}

// SendCommand send passed command to nhc
func SendCommand(cmd string) error {
	conn, _ := ConnectNhc(&config.Conf.NhcConfig)
	// no error handling as connect will exit in case of issue
	log.Debug("received command: ", cmd)
	fmt.Fprintf(conn, cmd+"\n")
	return nil
}
<commit_msg>bug fix: NHC events not catched by the listener<commit_after>package server

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/mch1307/gomotics/config"
	"github.com/mch1307/gomotics/db"
	"github.com/mch1307/gomotics/log"
	"github.com/mch1307/gomotics/types"
)

const (
	// SystemInfo holds NHC startevents
	SystemInfo = "{\"cmd\":\"systeminfo\"}"
	// RegisterCMD holds NHC startevents
	RegisterCMD = "{\"cmd\":\"startevents\"}"
	// ListActions holds NHC listactions
	ListActions = "{\"cmd\":\"listactions\"}"
	// ListLocations holds NHC listlocations
	ListLocations = "{\"cmd\":\"listlocations\"}"
	// ListEnergies holds NHC listenergy
	ListEnergies = "{\"cmd\":\"listenergy\"}"
	// ListThermostats holds NHC listthermostat
	ListThermostats = "{\"cmd\":\"listthermostat\"}"
)

var (
	nhcActions   []types.Action
	nhcLocations []types.Location
	nhcEvent     []types.Event
	nhcInfo      types.NHCSystemInfo
	nhcMessage   types.Message
)

// SimpleCmd type holding a nhc command
type SimpleCmd struct {
	Cmd   string `json:"cmd"`
	ID    int    `json:"id"`
	Value int    `json:"value1"`
}

// Stringify return the string version of SimpleCmd
func (sc SimpleCmd) Stringify() string {
	tmp, _ := json.Marshal(sc)
	return string(tmp)
}

// NhcInit sends list commands to NHC in order to get all equipments
func NhcInit(cfg *config.NhcConf) {
	if len(cfg.Host) == 0 {
		log.Debug("no nhc host in conf, starting discovery")
		nhcHost := Discover()
		log.Debug("discover returned ", nhcHost)
		config.Conf.NhcConfig.Host = nhcHost.String()
		cfg.Host = config.Conf.NhcConfig.Host
	}
	if cfg.Port == 0 {
		config.Conf.NhcConfig.Port = 8000
		cfg.Port = config.Conf.NhcConfig.Port

	}
	conn, err := ConnectNhc(cfg)
	if err != nil {
		log.Fatalf("Unable to connect to NHC host: %v. Error: %v", cfg.Host, err)
	}
	reader := json.NewDecoder(conn)
	fmt.Println("Connected to NHC unit: ", cfg.Host)
	log.Info("Connected to NHC unit: ", cfg.Host)

	// sends systeminfo command to NHC
	fmt.Fprintf(conn, SystemInfo+"\n")
	if err := reader.Decode(&nhcMessage); err != nil {
		log.Fatalf("Unable to parse NHC SystemInfo message: %v", err)
	}
	Route(&nhcMessage)

	// sends listlocations command to NHC
	fmt.Fprintf(conn, ListLocations+"\n")
	if err := reader.Decode(&nhcMessage); err != nil {
		log.Fatalf("Unable to parse NHC ListLocations message: %v", err)
	}
	Route(&nhcMessage)

	// sends listActions command to NHC
	fmt.Fprintf(conn, ListActions+"\n")
	if err := reader.Decode(&nhcMessage); err != nil {
		log.Fatalf("Unable to parse NHC ListActions message: %v", err)
	}
	Route(&nhcMessage)

	defer conn.Close()
	// Build the nhc collection
	db.BuildNHCItems()
	if config.Conf.ServerConfig.LogLevel == "DEBUG" {
		db.Dump()
	}
	log.Info("Nhc init done")
}

// NhcListener start a connection to nhc host, register itself
// to receive and route all messages from nhc broadcast
func NhcListener() {
	var nhcMessage types.Message

	conn, err := ConnectNhc(&config.Conf.NhcConfig)
	if err != nil {
		log.Fatal("Fatal error connecting to NHC: ", err)
	}

	fmt.Fprintf(conn, RegisterCMD+"\n")
	reader := json.NewDecoder(conn)
	//reader := bufio.NewReader(conn)
	//tp := textproto.NewReader(reader)

	for {
		//line, _ := tp.ReadLine()
		//if err := json.Unmarshal([]byte(line), &nhcMessage); err != nil {
		if err := reader.Decode(&nhcMessage); err != nil {
			log.Errorf("error decoding NHC message %v", err)
		}
		log.Debug(string(nhcMessage.Data))
		if nhcMessage.Cmd == "startevents" {
			log.Info("listener registered")
			nhcMessage.Cmd = "dropme"
		} else {
			log.Debug("received ", &nhcMessage.Cmd)
			Route(&nhcMessage)
		}
	}
}

// Route parse and route incoming message the right handler
func Route(msg *types.Message) {
	if msg.Cmd == "listlocations" {
		/* Commented err handling as all incoming msg have already been json parsed once */
		/* 		if err := json.Unmarshal(msg.Data, &nhcLocations); err != nil {
			log.Fatal(err)
		} */
		_ = json.Unmarshal(msg.Data, &nhcLocations)
		for idx := range nhcLocations {
			db.SaveNHCLocation(nhcLocations[idx])
			//db.SaveItem(nhcLocations[idx])
		}
	} else if msg.Cmd == "listactions" {
		/* 		if err := json.Unmarshal(msg.Data, &nhcActions); err != nil {
			log.Fatal(err)
		} */
		_ = json.Unmarshal(msg.Data, &nhcActions)
		for idx := range nhcActions {
			db.SaveNHCAction(nhcActions[idx])
			//db.SaveItem(nhcActions[idx])
		}
	} else if msg.Event == "listactions" {
		/* 		if err := json.Unmarshal(msg.Data, &nhcEvent); err != nil {
			log.Errorf("unable to parse message %v, err: %v", msg.Data, err)
		} */
		msg.Event = "dropme"
		_ = json.Unmarshal(msg.Data, &nhcEvent)
		for _, rec := range nhcEvent {
			res := db.ProcessNHCEvent(rec)
			var updatedItem types.NHCItem
			_ = json.Unmarshal(res, &updatedItem)
			if config.Conf.JeedomConfig.Enabled && updatedItem.JeedomUpdState != "" {
				go UpdateJeedomState(updatedItem)
			}
			WSPool.Broadcast <- res
			//db.SaveItem(nhcEvent[idx])
		}
	} else if msg.Cmd == "systeminfo" {
		msg.Cmd = "dropme"
		_ = json.Unmarshal(msg.Data, &nhcInfo)
		db.SaveNhcSysInfo(nhcInfo)
	}
}

// Discover discover NHC controller by sending UDP pkg on port 10000
// return NHC IP address and boolean
func Discover() net.IP {
	//	var err error
	var nhcConnectString net.IP
	var targetAddr *net.UDPAddr
	data, _ := hex.DecodeString("44")
	addr := net.UDPAddr{IP: net.IPv4bcast, Port: 10000}

	conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: GetOutboundIP(), Port: 18043})
	if err != nil {
		fmt.Println("err connect: ", err)
	}
	//defer conn.Close()
	_, err = conn.WriteToUDP(data, &addr)

	b := make([]byte, 1024)
	// goroutine for reading broadcast result
	go func() {
		for {
			defer conn.Close()
			_, targetAddr, err = conn.ReadFromUDP(b)
			if err != nil {
				log.Warnf("Error: UDP read error: %v", err)
				continue
			}
			// test "nhc" connection to replying IP to make sure targetAddr is a NHC controller
			connectString := net.TCPAddr{IP: targetAddr.IP, Port: 8000}
			if err == nil {
				_, err := net.DialTCP("tcp", nil, &connectString)
				//defer nhConn.Close()
				if err == nil {
					nhcConnectString = connectString.IP
					log.Debug("return IP: ", string(nhcConnectString))
					return
				}
			}
			defer conn.Close()
			//			return
		}
	}()
	time.Sleep(time.Second * 6)
	return nhcConnectString
}

//ConnectNhc establish connection to Niko Home Control IP module
func ConnectNhc(cfg *config.NhcConf) (conn *net.TCPConn, err error) {

	connectString, err := net.ResolveTCPAddr("tcp", cfg.Host+":"+strconv.Itoa(cfg.Port))
	if err != nil {
		log.Error("connNhc ", err)
		return nil, err
	}

	conn, err = net.DialTCP("tcp", nil, connectString)
	if err != nil {
		log.Error("error connecting to nhc: ", err)
	}
	return conn, err
}

// SendCommand send passed command to nhc
func SendCommand(cmd string) error {
	conn, _ := ConnectNhc(&config.Conf.NhcConfig)
	// no error handling as connect will exit in case of issue
	log.Debug("received command: ", cmd)
	fmt.Fprintf(conn, cmd+"\n")
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"github.com/monnand/dhkx"
)

// struct for holding room info
type Room struct {
	name        string
	discription string
	password    string
	maxClients  int
	welcomeMsg  string
}

// struct for holding client info
type Client struct {
	connection net.Conn
	nick       string
	clientKey  string
}

var rooms []Room
var clientRoom map[Client]Room
var publicKeyCode string

// clear the terminal screen
func clear() {
	switch runtime.GOOS {
	case "linux":
		cmd := exec.Command("clear")
		cmd.Stdout = os.Stdout
		cmd.Run()
	case "windows":
		cmd := exec.Command("cmd", "/c", "cls")
		cmd.Stdout = os.Stdout
		cmd.Run()
	default:
		fmt.Println("Attempted to clear terminal, but OS is not supported.")
	}
}

// init
func setup() {
	publicKeyCode = "ssd990=+?¡][ªs)(sdª]ßð=S)]"
	clientRoom = make(map[Client]Room)
	makeRoom("Lobby", "Welcome to Lobby")
	makeRoom("TestRoom", "Welcome to TestRoom")
}

// check if []byte contains specific byte
func contains(s []byte, e byte) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

// exchange key with client
func exchangeKeys(c Client) {

	var clientPublicKey []byte

	// generate private key
	g, _ := dhkx.GetGroup(0)
	serverPrivateKey, _ := g.GeneratePrivateKey(nil)

	// make sure the key does not contain '\n' or '%'
	for {
		if contains(serverPrivateKey.Bytes(), byte('\n')) || contains(serverPrivateKey.Bytes(), byte('%')) {
			newKey, _ := g.GeneratePrivateKey(nil)
			serverPrivateKey = newKey
		} else {
			break
		}
	}

	// generate public key
	serverPublicKey := serverPrivateKey.Bytes()

	// listening for client public key
	for {
		message, _ := bufio.NewReader(c.connection).ReadString('\n')
		if len(message) > len(publicKeyCode) {
			if message[0:len(publicKeyCode)] == publicKeyCode {
				clientPublicKey = []byte(message[len(publicKeyCode) : len(message)-1])
				break
			}
		}
	}

	// sending server public key
	msg := publicKeyCode + string(serverPublicKey) + "\n"
	fmt.Fprintf(c.connection, msg)

	// finding common key
	pubKey := dhkx.NewPublicKey(clientPublicKey)
	k, _ := g.ComputeKey(pubKey, serverPrivateKey)
	c.clientKey = string(k.Bytes()[0:32])

	// start the client thread and place in room 0 (Lobby)
	c.startThread()
	fmt.Println(c.connection.RemoteAddr().String() + " connected")
	switchRoom(c, rooms[0])
}

func main() {
	setup()
	startServer()
}

// start server listening
func startServer() {
	port := ":8081"
	ln, _ := net.Listen("tcp", port)
	clear()
	fmt.Println("Server is listening on " + port)

	// accept incomming connectoins, make new client struct and start thread for exchanging keys
	for {
		conn, _ := ln.Accept()
		newClient := Client{connection: conn}
		go exchangeKeys(newClient)
	}
}

// listen for messages from client
func (c Client) listener() {
	quitting := false

	for {
		message, _ := bufio.NewReader(c.connection).ReadString('\n')
		if len(message) > 0 {
			msg := message[0 : len(message)-1]

			// decrypt message
			msgDecrypted := decrypt([]byte(c.clientKey), msg)

			// if client wants to quit
			if msgDecrypted == "!quit" {
				quitting = true
			}

			// if not quitting, print message, check for commands
			if !quitting {
				fmt.Print(c.connection.RemoteAddr().String() + ": " + msgDecrypted)

				if !checkForCmd(c, msgDecrypted) {
					// for each client in the same room, encrypt the message
					// with their key and send
					for mapKey, value := range clientRoom {
						if mapKey != c && value == clientRoom[c] {
							mapKey.sendEncrypted(msgDecrypted)
						}
					}
				}
			} else {
				// delete client from the map, close connection and print
				delete(clientRoom, c)
				c.connection.Close()
				fmt.Println(c.connection.RemoteAddr().String() + " has disconnected")
				break
			}
		}
	}
}

// send message to client
func (c *Client) send(message []byte) {
	c.connection.Write(message)
}

// send encrypted message to client
func (c *Client) sendEncrypted(message string) {
	cryptMsg := encrypt([]byte(c.clientKey), message)
	c.connection.Write([]byte(cryptMsg + "\n"))
}

// start a listener thread for a client
func (c *Client) startThread() {
	go c.listener()
}

// check messsage for commands
func checkForCmd(client Client, msg string) bool {
	if len(msg) > 1 {
		words := strings.Split(msg[0:len(msg)-1], " ")
		switch words[0] {
		// !room displays a list of available rooms
		case "!room":
			// if more than 1 word = we have arguments
			if len(words) > 1 {
				// if attempting to join the room client is already in
				if words[1] == clientRoom[client].name {
					message := "You are already in this room\nType !room to get a list of other available chatrooms\n"
					client.sendEncrypted(message)
				} else {
					// look for the room the client wants to join, and join if found
					for _, element := range rooms {
						if element.name == words[1] {
							client.sendEncrypted("Switching room: " + element.name + "\n")
							switchRoom(client, element)
						}
					}
				}
			} else {
				roomMsg := ""
				for _, element := range rooms {
					roomMsg += " - " + element.name + "\n"
				}
				client.sendEncrypted(roomMsg)
			}
			return true
		}
	}
	return false
}

// client switch room
func switchRoom(client Client, room Room) {
	// tell clients in the room that we are leaving that
	// and clients in the new room that we are joining
	for k, v := range clientRoom {
		if k != client && v == room {
			k.sendEncrypted("\033[1m" + client.connection.RemoteAddr().String() + "\033[0m" + " has joined " + room.name + "\n")
		}
		if k != client && v != room {
			k.sendEncrypted("\033[1m" + client.connection.RemoteAddr().String() + "\033[0m" + " has left " + clientRoom[client].name + "\n")
		}
	}

	// "\033[1m"+nick+"\033[0m"

	// change room
	clientRoom[client] = room
	// send the rooms welcome message
	client.sendEncrypted("\033[1m" + room.welcomeMsg + "\033[0m" + "\n")
}

// make a new room
func makeRoom(name string, welcomeMsg string) {
	newRoom := Room{name: name, welcomeMsg: welcomeMsg}
	rooms = append(rooms, newRoom)
}

// create key
func createKey() []byte {
	randKey := make([]byte, 32)
	_, err := rand.Read(randKey)
	if err != nil {
		panic(err)
	}
	return randKey
}

// encrypt message
func encrypt(key []byte, text string) string {
	plaintext := []byte(text)

	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	return base64.URLEncoding.EncodeToString(ciphertext)
}

// decrypt message
func decrypt(key []byte, cryptoText string) string {
	ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText)

	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	if len(ciphertext) < aes.BlockSize {
		panic("Ciphertext too short")
	}

	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)

	stream.XORKeyStream(ciphertext, ciphertext)

	return fmt.Sprintf("%s", ciphertext)
}
<commit_msg>forgot bold on room name<commit_after>package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"github.com/monnand/dhkx"
)

// struct for holding room info
type Room struct {
	name        string
	discription string
	password    string
	maxClients  int
	welcomeMsg  string
}

// struct for holding client info
type Client struct {
	connection net.Conn
	nick       string
	clientKey  string
}

var rooms []Room
var clientRoom map[Client]Room
var publicKeyCode string

// clear the terminal screen
func clear() {
	switch runtime.GOOS {
	case "linux":
		cmd := exec.Command("clear")
		cmd.Stdout = os.Stdout
		cmd.Run()
	case "windows":
		cmd := exec.Command("cmd", "/c", "cls")
		cmd.Stdout = os.Stdout
		cmd.Run()
	default:
		fmt.Println("Attempted to clear terminal, but OS is not supported.")
	}
}

// init
func setup() {
	publicKeyCode = "ssd990=+?¡][ªs)(sdª]ßð=S)]"
	clientRoom = make(map[Client]Room)
	makeRoom("Lobby", "Welcome to Lobby")
	makeRoom("TestRoom", "Welcome to TestRoom")
}

// check if []byte contains specific byte
func contains(s []byte, e byte) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

// exchange key with client
func exchangeKeys(c Client) {

	var clientPublicKey []byte

	// generate private key
	g, _ := dhkx.GetGroup(0)
	serverPrivateKey, _ := g.GeneratePrivateKey(nil)

	// make sure the key does not contain '\n' or '%'
	for {
		if contains(serverPrivateKey.Bytes(), byte('\n')) || contains(serverPrivateKey.Bytes(), byte('%')) {
			newKey, _ := g.GeneratePrivateKey(nil)
			serverPrivateKey = newKey
		} else {
			break
		}
	}

	// generate public key
	serverPublicKey := serverPrivateKey.Bytes()

	// listening for client public key
	for {
		message, _ := bufio.NewReader(c.connection).ReadString('\n')
		if len(message) > len(publicKeyCode) {
			if message[0:len(publicKeyCode)] == publicKeyCode {
				clientPublicKey = []byte(message[len(publicKeyCode) : len(message)-1])
				break
			}
		}
	}

	// sending server public key
	msg := publicKeyCode + string(serverPublicKey) + "\n"
	fmt.Fprintf(c.connection, msg)

	// finding common key
	pubKey := dhkx.NewPublicKey(clientPublicKey)
	k, _ := g.ComputeKey(pubKey, serverPrivateKey)
	c.clientKey = string(k.Bytes()[0:32])

	// start the client thread and place in room 0 (Lobby)
	c.startThread()
	fmt.Println(c.connection.RemoteAddr().String() + " connected")
	switchRoom(c, rooms[0])
}

func main() {
	setup()
	startServer()
}

// start server listening
func startServer() {
	port := ":8081"
	ln, _ := net.Listen("tcp", port)
	clear()
	fmt.Println("Server is listening on " + port)

	// accept incomming connectoins, make new client struct and start thread for exchanging keys
	for {
		conn, _ := ln.Accept()
		newClient := Client{connection: conn}
		go exchangeKeys(newClient)
	}
}

// listen for messages from client
func (c Client) listener() {
	quitting := false

	for {
		message, _ := bufio.NewReader(c.connection).ReadString('\n')
		if len(message) > 0 {
			msg := message[0 : len(message)-1]

			// decrypt message
			msgDecrypted := decrypt([]byte(c.clientKey), msg)

			// if client wants to quit
			if msgDecrypted == "!quit" {
				quitting = true
			}

			// if not quitting, print message, check for commands
			if !quitting {
				fmt.Print(c.connection.RemoteAddr().String() + ": " + msgDecrypted)

				if !checkForCmd(c, msgDecrypted) {
					// for each client in the same room, encrypt the message
					// with their key and send
					for mapKey, value := range clientRoom {
						if mapKey != c && value == clientRoom[c] {
							mapKey.sendEncrypted(msgDecrypted)
						}
					}
				}
			} else {
				// delete client from the map, close connection and print
				delete(clientRoom, c)
				c.connection.Close()
				fmt.Println(c.connection.RemoteAddr().String() + " has disconnected")
				break
			}
		}
	}
}

// send message to client
func (c *Client) send(message []byte) {
	c.connection.Write(message)
}

// send encrypted message to client
func (c *Client) sendEncrypted(message string) {
	cryptMsg := encrypt([]byte(c.clientKey), message)
	c.connection.Write([]byte(cryptMsg + "\n"))
}

// start a listener thread for a client
func (c *Client) startThread() {
	go c.listener()
}

// check messsage for commands
func checkForCmd(client Client, msg string) bool {
	if len(msg) > 1 {
		words := strings.Split(msg[0:len(msg)-1], " ")
		switch words[0] {
		// !room displays a list of available rooms
		case "!room":
			// if more than 1 word = we have arguments
			if len(words) > 1 {
				// if attempting to join the room client is already in
				if words[1] == clientRoom[client].name {
					message := "You are already in this room\nType !room to get a list of other available chatrooms\n"
					client.sendEncrypted(message)
				} else {
					// look for the room the client wants to join, and join if found
					for _, element := range rooms {
						if element.name == words[1] {
							client.sendEncrypted("Switching room: " + element.name + "\n")
							switchRoom(client, element)
						}
					}
				}
			} else {
				roomMsg := ""
				for _, element := range rooms {
					roomMsg += " - " + element.name + "\n"
				}
				client.sendEncrypted(roomMsg)
			}
			return true
		}
	}
	return false
}

// client switch room
func switchRoom(client Client, room Room) {
	// tell clients in the room that we are leaving that
	// and clients in the new room that we are joining
	for k, v := range clientRoom {
		if k != client && v == room {
			k.sendEncrypted("\033[1m" + client.connection.RemoteAddr().String() + "\033[0m" + " has joined " + "\033[1m" + room.name + "\033[0m" + "\n")
		}
		if k != client && v != room {
			k.sendEncrypted("\033[1m" + client.connection.RemoteAddr().String() + "\033[0m" + " has left " + "\033[1m" + clientRoom[client].name + "\033[0m" + "\n")
		}
	}

	// "\033[1m"+nick+"\033[0m"

	// change room
	clientRoom[client] = room
	// send the rooms welcome message
	client.sendEncrypted("\033[1m" + room.welcomeMsg + "\033[0m" + "\n")
}

// make a new room
func makeRoom(name string, welcomeMsg string) {
	newRoom := Room{name: name, welcomeMsg: welcomeMsg}
	rooms = append(rooms, newRoom)
}

// create key
func createKey() []byte {
	randKey := make([]byte, 32)
	_, err := rand.Read(randKey)
	if err != nil {
		panic(err)
	}
	return randKey
}

// encrypt message
func encrypt(key []byte, text string) string {
	plaintext := []byte(text)

	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	return base64.URLEncoding.EncodeToString(ciphertext)
}

// decrypt message
func decrypt(key []byte, cryptoText string) string {
	ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText)

	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	if len(ciphertext) < aes.BlockSize {
		panic("Ciphertext too short")
	}

	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)

	stream.XORKeyStream(ciphertext, ciphertext)

	return fmt.Sprintf("%s", ciphertext)
}
<|endoftext|>