<commit_before>package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/dogenzaka/rotator"
	"github.com/getlantern/appdir"
	"github.com/getlantern/golog"
	"github.com/getlantern/wfilter"
	"github.com/segmentio/go-loggly"
)

var (
	LogTimestampFormat = "Jan 02 15:04:05.000"
	log                = golog.LoggerFor("flashlight")
	logglyToken        string
)

func configureLogging() *rotator.SizeRotator {
	logdir := appdir.Logs("Lantern")
	log.Debugf("Placing logs in %v", logdir)
	if _, err := os.Stat(logdir); err != nil {
		if os.IsNotExist(err) {
			// Create log dir
			if err := os.MkdirAll(logdir, 0755); err != nil {
				log.Fatalf("Unable to create logdir at %s: %s", logdir, err)
			}
		}
	}
	file := rotator.NewSizeRotator(filepath.Join(logdir, "lantern.log"))
	// Set log files to 1 MB
	file.RotationSize = 1 * 1024 * 1024
	// Keep up to 20 log files
	file.MaxRotation = 20

	remoteWriter := logglyErrorWriter{loggly.New(logglyToken)}
	// Loggly has its own timestamp so don't bother adding it in message,
	// moreover, golog always write each line in whole, so we need not to care about line breaks.
	errorOut := NonStopWriter(timestamped(NonStopWriter(os.Stderr, file)), remoteWriter)
	debugOut := timestamped(NonStopWriter(os.Stdout, file))
	golog.SetOutputs(errorOut, debugOut)
	return file
}

// timestamped adds a timestamp to the beginning of log lines
func timestamped(orig io.Writer) io.Writer {
	return wfilter.LinePrepender(orig, func(w io.Writer) (int, error) {
		return fmt.Fprintf(w, "%s - ", time.Now().In(time.UTC).Format(LogTimestampFormat))
	})
}

type logglyErrorWriter struct {
	l *loggly.Client
}

func (w logglyErrorWriter) Write(b []byte) (int, error) {
	return writeToLoggly(w.l, "ERROR", string(b))
}

func writeToLoggly(l *loggly.Client, level string, msg string) (int, error) {
	extra := map[string]string{
		"logLevel":  level,
		"osName":    runtime.GOOS,
		"osArch":    runtime.GOARCH,
		"osVersion": "",
		"language":  "",
		"country":   "",
		"timeZone":  "",
		"version":   version,
	}
	m := loggly.Message{
		"extra":   extra,
		"message": msg,
	}
	err := l.Send(m)
	if err != nil {
		return 0, err
	}
	return len(msg), nil
}

type nonStopWriter struct {
	writers []io.Writer
}

func (t *nonStopWriter) Write(p []byte) (n int, err error) {
	for _, w := range t.writers {
		n, _ = w.Write(p)
	}
	return len(p), nil
}

// NonStopWriter creates a writer that duplicates its writes to all the
// provided writers, similar to the Unix tee(1) command.
func NonStopWriter(writers ...io.Writer) io.Writer {
	w := make([]io.Writer, len(writers))
	copy(w, writers)
	return &nonStopWriter{w}
}
<commit_msg>code review updates<commit_after>package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"time"

	"github.com/dogenzaka/rotator"
	"github.com/getlantern/appdir"
	"github.com/getlantern/golog"
	"github.com/getlantern/jibber_jabber"
	"github.com/getlantern/wfilter"
	"github.com/segmentio/go-loggly"
)

var (
	LogTimestampFormat = "Jan 02 15:04:05.000"
	log                = golog.LoggerFor("flashlight")
	logglyToken        string
	versionToLoggly    = fmt.Sprintf("%v (%v)", version, buildDate)
)

func configureLogging() *rotator.SizeRotator {
	logdir := appdir.Logs("Lantern")
	log.Debugf("Placing logs in %v", logdir)
	if _, err := os.Stat(logdir); err != nil {
		if os.IsNotExist(err) {
			// Create log dir
			if err := os.MkdirAll(logdir, 0755); err != nil {
				log.Fatalf("Unable to create logdir at %s: %s", logdir, err)
			}
		}
	}
	file := rotator.NewSizeRotator(filepath.Join(logdir, "lantern.log"))
	// Set log files to 1 MB
	file.RotationSize = 1 * 1024 * 1024
	// Keep up to 20 log files
	file.MaxRotation = 20

	remoteWriter := logglyErrorWriter{loggly.New(logglyToken)}
	// Loggly has its own timestamp so don't bother adding it in message,
	// moreover, golog always write each line in whole, so we need not to care about line breaks.
	errorOut := NonStopWriter(timestamped(NonStopWriter(os.Stderr, file)), remoteWriter)
	debugOut := timestamped(NonStopWriter(os.Stdout, file))
	golog.SetOutputs(errorOut, debugOut)
	return file
}

// timestamped adds a timestamp to the beginning of log lines
func timestamped(orig io.Writer) io.Writer {
	return wfilter.LinePrepender(orig, func(w io.Writer) (int, error) {
		return fmt.Fprintf(w, "%s - ", time.Now().In(time.UTC).Format(LogTimestampFormat))
	})
}

type logglyErrorWriter struct {
	l *loggly.Client
}

func (w logglyErrorWriter) Write(b []byte) (int, error) {
	return writeToLoggly(w.l, "ERROR", string(b))
}

func writeToLoggly(l *loggly.Client, level string, msg string) (int, error) {
	lang, _ := jibber_jabber.DetectLanguage()
	country, _ := jibber_jabber.DetectTerritory()
	extra := map[string]string{
		"logLevel":  level,
		"osName":    runtime.GOOS,
		"osArch":    runtime.GOARCH,
		"osVersion": "",
		"language":  lang,
		"country":   country,
		"timeZone":  "",
		"version":   versionToLoggly,
	}
	m := loggly.Message{
		"extra":   extra,
		"message": msg,
	}
	err := l.Send(m)
	if err != nil {
		return 0, err
	}
	return len(msg), nil
}

type nonStopWriter struct {
	writers []io.Writer
}

func (t *nonStopWriter) Write(p []byte) (n int, err error) {
	for _, w := range t.writers {
		n, _ = w.Write(p)
	}
	return len(p), nil
}

// NonStopWriter creates a writer that duplicates its writes to all the
// provided writers, even if errors encountered while writting.
func NonStopWriter(writers ...io.Writer) io.Writer {
	w := make([]io.Writer, len(writers))
	copy(w, writers)
	return &nonStopWriter{w}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"github.com/jhillyerd/go.enmime"
	"github.com/mattermost/platform/model"
	"github.com/mxk/go-imap/imap"
	"github.com/paulrosania/go-charset/charset"
	_ "github.com/paulrosania/go-charset/data"
	"io/ioutil"
	"log"
	"mime/multipart"
	"mime/quotedprintable"
	"net/mail"
	"os"
	"regexp"
	"strings"
	"time"
)

type MatterMail struct {
	cfg        *config
	imapClient *imap.Client
	logI       *log.Logger
	logE       *log.Logger
}

func (m *MatterMail) tryTime(message string, fn func() error) {
	if err := fn(); err != nil {
		m.logI.Println(message, err, "\n", "Try again in 30s")
		time.Sleep(30 * time.Second)
		fn()
	}
}

func (m *MatterMail) LogoutImapClient() {
	if m.imapClient != nil {
		m.imapClient.Logout(time.Second * 5)
	}
}

func (m *MatterMail) CheckImapConnection() error {
	if m.imapClient != nil && (m.imapClient.State() == imap.Auth || m.imapClient.State() == imap.Selected) {
		return nil
	}

	var err error
	//Start connection with server
	m.imapClient, err = imap.Dial(m.cfg.ImapServer)

	if err != nil {
		m.logE.Println("Unable to connect:", err)
		return err
	}

	m.logI.Printf("Connected with %q\n", m.cfg.ImapServer)

	_, err = m.imapClient.Login(m.cfg.Email, m.cfg.EmailPass)
	if err != nil {
		m.logE.Println("Unable to login:", m.cfg.Email)
		return err
	}

	return nil
}

//Check if exist a new mail and post it
func (m *MatterMail) CheckNewMails() error {

	if err := m.CheckImapConnection(); err != nil {
		return err
	}

	var (
		cmd *imap.Command
		rsp *imap.Response
	)

	// Open a mailbox (synchronous command - no need for imap.Wait)
	m.imapClient.Select("INBOX", false)

	var specs []imap.Field
	specs = append(specs, "UNSEEN")
	seq := &imap.SeqSet{}

	// get headers and UID for UnSeen message in src inbox...
	cmd, err := imap.Wait(m.imapClient.UIDSearch(specs...))
	if err != nil {
		m.logE.Println("UIDSearch:")
		return err
	}

	for _, rsp := range cmd.Data {
		for _, uid := range rsp.SearchResults() {
			seq.AddNum(uid)
		}
	}

	// no new messages
	if seq.Empty() {
		return nil
	}

	cmd, _ = m.imapClient.Fetch(seq, "FLAGS", "INTERNALDATE", "UID", "RFC822.HEADER", "BODY[]")

	for cmd.InProgress() {
		// Wait for the next response (no timeout)
		m.imapClient.Recv(-1)

		// Process command data
		for _, rsp = range cmd.Data {
			msgFields := rsp.MessageInfo().Attrs
			header := imap.AsBytes(msgFields["BODY[]"])
			if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil {
				if err := m.PostMail(msg); err != nil {
					return err
				}
			}
		}
		cmd.Data = nil
	}

	// Check command completion status
	if rsp, err := cmd.Result(imap.OK); err != nil {
		if err == imap.ErrAborted {
			m.logE.Println("Fetch command aborted")
			return err
		} else {
			m.logE.Println("Fetch error:", rsp.Info)
			return err
		}
	}

	cmd.Data = nil

	//Mark all messages seen
	_, err = imap.Wait(m.imapClient.UIDStore(seq, "+FLAGS.SILENT", `\Seen`))
	if err != nil {
		m.logE.Printf("Error UIDStore \\Seen")
		return err
	}
	return nil
}

//Change to state idle in imap server
func (m *MatterMail) IdleMailBox() error {

	if err := m.CheckImapConnection(); err != nil {
		return err
	}

	// Open a mailbox (synchronous command - no need for imap.Wait)
	m.imapClient.Select("INBOX", false)

	_, err := m.imapClient.Idle()
	if err != nil {
		return err
	}

	defer m.imapClient.IdleTerm()

	for {
		err := m.imapClient.Recv(time.Second)
		if err == nil {
			break
		}
	}
	return nil
}

func addPart(client *model.Client, filename string, content *[]byte, writer *multipart.Writer) error {
	part, err := writer.CreateFormFile("files", filename)
	if err != nil {
		return err
	}

	_, err = part.Write(*content)
	if err != nil {
		return err
	}
	return nil
}

//Create a post in Mattermost
func (m *MatterMail) postMessage(client *model.Client, channel_id string, message string, filenames *[]string) error {
	post := &model.Post{ChannelId: channel_id, Message: message}

	if len(*filenames) > 0 {
		post.Filenames = *filenames
	}

	res, err := client.CreatePost(post)
	if res == nil {
		return err
	}

	return nil
}

//Post files and message in Mattermost server
func (m *MatterMail) PostFile(message string, emailname string, emailbody *string, attach *[]enmime.MIMEPart) error {

	client := model.NewClient(m.cfg.Server)

	if _, err := client.LoginByEmail(m.cfg.Team, m.cfg.MattermostUser, m.cfg.MattermostPass); err != nil {
		return err
	}

	m.logI.Println("Post new message")

	defer client.Logout()

	//Discover channel id by channel name
	var channel_id string

	rget := client.Must(client.GetChannels("")).Data.(*model.ChannelList)

	nameMatch := false
	for _, c := range rget.Channels {
		if c.Name == m.cfg.Channel {
			channel_id = c.Id
			nameMatch = true
			break
		}
	}

	if !nameMatch {
		return fmt.Errorf("Did not find channel with name %v", m.cfg.Channel)
	}

	if len(*attach) == 0 && len(emailname) == 0 {
		return m.postMessage(client, channel_id, message, nil)
	}

	buf := &bytes.Buffer{}
	writer := multipart.NewWriter(buf)

	var email []byte
	if len(emailname) > 0 {
		email = []byte(*emailbody)
		if err := addPart(client, emailname, &email, writer); err != nil {
			return err
		}
	}

	for _, a := range *attach {
		email = a.Content()
		if err := addPart(client, a.FileName(), &email, writer); err != nil {
			return err
		}
	}

	field, err := writer.CreateFormField("channel_id")
	if err != nil {
		return err
	}

	_, err = field.Write([]byte(channel_id))
	if err != nil {
		return err
	}

	err = writer.Close()
	if err != nil {
		return err
	}

	resp, err := client.UploadFile("/files/upload", buf.Bytes(), writer.FormDataContentType())
	if resp == nil {
		return err
	}

	return m.postMessage(client, channel_id, message, &resp.Data.(*model.FileUploadResponse).Filenames)
}

//Read number of lines of string
func readLines(s string, nmax int) string {
	lines := regexp.MustCompile("\r\n|\n").Split(s, nmax+1)

	if nmax < len(lines) {
		return strings.Join(lines[:nmax], "\n")
	}
	return strings.Join(lines[:], "\n")
}

//Replace cid:**** by embedded base64 image
func replaceCID(html *string, part *enmime.MIMEPart) string {
	cid := strings.Replace((*part).Header().Get("Content-ID"), "<", "", -1)
	cid = strings.Replace(cid, ">", "", -1)

	if len(cid) == 0 {
		return *html
	}

	b64 := "data:" + (*part).ContentType() + ";base64," + base64.StdEncoding.EncodeToString((*part).Content())

	return strings.Replace(*html, "cid:"+cid, b64, -1)
}

//Decode non ASCII header string RFC 1342
//encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
func NonASCII(encoded string) string {

	regex_rfc1342, _ := regexp.Compile(`=\?[^\?]*\?.\?[^\?]*\?=`)

	result := regex_rfc1342.ReplaceAllStringFunc(encoded, func(encoded string) string {
		//0 utf 1 B/Q 2 code
		v := strings.Split(encoded, "?")[1:4]
		var decoded string
		switch strings.ToLower(v[1]) {
		case "b": //Base64
			data, err := base64.StdEncoding.DecodeString(v[2])
			if err != nil {
				log.Println("Error decode Base64", err)
				return encoded
			}

			decoded = string(data)

		case "q": //Quoted-Printable
			data, err := ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(v[2])))
			if err != nil {
				log.Println("Error decode Quoted-Printable", err)
				return encoded
			}
			decoded = string(data)

		default:
			log.Println("Unknow encoding " + v[1])
			return encoded
		}

		//Decode charset
		r, err := charset.NewReader(strings.ToLower(v[0]), strings.NewReader(decoded))
		if err != nil {
			log.Println("Error decode charset", err)
			return encoded
		}

		result, _ := ioutil.ReadAll(r)

		return string(result)
	})

	return result
}

//Post an email in Mattermost
func (m *MatterMail) PostMail(msg *mail.Message) error {
	mime, _ := enmime.ParseMIMEBody(msg) // Parse message body with enmime

	var emailname, emailbody string
	if len(mime.Html) > 0 {
		emailname = "email.html"
		emailbody = mime.Html
		for _, p := range mime.Inlines {
			emailbody = replaceCID(&emailbody, &p)
		}

		for _, p := range mime.OtherParts {
			emailbody = replaceCID(&emailbody, &p)
		}

	} else if len(mime.Text) > 0 {
		emailname = "email.txt"
		emailbody = mime.Text
	}

	// read only some lines of text
	partmessage := readLines(mime.Text, 5)

	if partmessage != mime.Text && len(partmessage) > 0 {
		partmessage += " ..."
	}

	message := fmt.Sprintf(m.cfg.MailTemplate, NonASCII(msg.Header.Get("From")), mime.GetHeader("Subject"), partmessage)

	return m.PostFile(message, emailname, &emailbody, &mime.Attachments)
}

func InitMatterMail(cfg *config) {
	//imap.DefaultLogger = log.New(os.Stdout, "", 0)
	//imap.DefaultLogMask = imap.LogConn | imap.LogRaw

	m := &MatterMail{
		cfg:  cfg,
		logI: log.New(os.Stdout, "INFO  "+cfg.Name+"\t", log.Ltime),
		logE: log.New(os.Stderr, "ERROR "+cfg.Name+"\t", log.Ltime),
	}

	defer m.LogoutImapClient()

	m.logI.Println("Checking new emails")
	m.tryTime("Error on check new email:", m.CheckNewMails)
	m.logI.Println("Waiting new messages")

	for {
		m.tryTime("Error Idle:", m.IdleMailBox)
		m.tryTime("Error on check new email:", m.CheckNewMails)
	}
}
<commit_msg>Update mattermail.go<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"github.com/jhillyerd/go.enmime"
	"github.com/mattermost/platform/model"
	"github.com/mxk/go-imap/imap"
	"github.com/paulrosania/go-charset/charset"
	_ "github.com/paulrosania/go-charset/data"
	"io/ioutil"
	"log"
	"mime/multipart"
	"mime/quotedprintable"
	"net/mail"
	"os"
	"regexp"
	"strings"
	"time"
)

type MatterMail struct {
	cfg        *config
	imapClient *imap.Client
	logI       *log.Logger
	logE       *log.Logger
}

func (m *MatterMail) tryTime(message string, fn func() error) {
	if err := fn(); err != nil {
		m.logI.Println(message, err, "\n", "Try again in 30s")
		time.Sleep(30 * time.Second)
		fn()
	}
}

func (m *MatterMail) LogoutImapClient() {
	if m.imapClient != nil {
		m.imapClient.Logout(time.Second * 5)
	}
}

func (m *MatterMail) CheckImapConnection() error {
	if m.imapClient != nil && (m.imapClient.State() == imap.Auth || m.imapClient.State() == imap.Selected) {
		return nil
	}

	var err error
	//Start connection with server
	m.imapClient, err = imap.Dial(m.cfg.ImapServer)

	if err != nil {
		m.logE.Println("Unable to connect:", err)
		return err
	}

	m.logI.Printf("Connected with %q\n", m.cfg.ImapServer)

	_, err = m.imapClient.Login(m.cfg.Email, m.cfg.EmailPass)
	if err != nil {
		m.logE.Println("Unable to login:", m.cfg.Email)
		return err
	}

	return nil
}

//Check if exist a new mail and post it
func (m *MatterMail) CheckNewMails() error {

	if err := m.CheckImapConnection(); err != nil {
		return err
	}

	var (
		cmd *imap.Command
		rsp *imap.Response
	)

	// Open a mailbox (synchronous command - no need for imap.Wait)
	m.imapClient.Select("INBOX", false)

	var specs []imap.Field
	specs = append(specs, "UNSEEN")
	seq := &imap.SeqSet{}

	// get headers and UID for UnSeen message in src inbox...
	cmd, err := imap.Wait(m.imapClient.UIDSearch(specs...))
	if err != nil {
		m.logE.Println("UIDSearch:")
		return err
	}

	for _, rsp := range cmd.Data {
		for _, uid := range rsp.SearchResults() {
			seq.AddNum(uid)
		}
	}

	// no new messages
	if seq.Empty() {
		return nil
	}

	cmd, _ = m.imapClient.Fetch(seq, "FLAGS", "INTERNALDATE", "UID", "RFC822.HEADER", "BODY[]")

	for cmd.InProgress() {
		// Wait for the next response (no timeout)
		m.imapClient.Recv(-1)

		// Process command data
		for _, rsp = range cmd.Data {
			msgFields := rsp.MessageInfo().Attrs
			header := imap.AsBytes(msgFields["BODY[]"])
			if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil {
				if err := m.PostMail(msg); err != nil {
					return err
				}
			}
		}
		cmd.Data = nil
	}

	// Check command completion status
	if rsp, err := cmd.Result(imap.OK); err != nil {
		if err == imap.ErrAborted {
			m.logE.Println("Fetch command aborted")
			return err
		} else {
			m.logE.Println("Fetch error:", rsp.Info)
			return err
		}
	}

	cmd.Data = nil

	//Mark all messages seen
	_, err = imap.Wait(m.imapClient.UIDStore(seq, "+FLAGS.SILENT", `\Seen`))
	if err != nil {
		m.logE.Printf("Error UIDStore \\Seen")
		return err
	}
	return nil
}

//Change to state idle in imap server
func (m *MatterMail) IdleMailBox() error {

	if err := m.CheckImapConnection(); err != nil {
		return err
	}

	// Open a mailbox (synchronous command - no need for imap.Wait)
	m.imapClient.Select("INBOX", false)

	_, err := m.imapClient.Idle()
	if err != nil {
		return err
	}

	defer m.imapClient.IdleTerm()

	for {
		err := m.imapClient.Recv(time.Second)
		if err == nil {
			break
		}
	}
	return nil
}

func addPart(client *model.Client, filename string, content *[]byte, writer *multipart.Writer) error {
	part, err := writer.CreateFormFile("files", filename)
	if err != nil {
		return err
	}

	_, err = part.Write(*content)
	if err != nil {
		return err
	}
	return nil
}

//Create a post in Mattermost
func (m *MatterMail) postMessage(client *model.Client, channel_id string, message string, filenames *[]string) error {
	post := &model.Post{ChannelId: channel_id, Message: message}

	if len(*filenames) > 0 {
		post.Filenames = *filenames
	}

	res, err := client.CreatePost(post)
	if res == nil {
		return err
	}

	return nil
}

//Post files and message in Mattermost server
func (m *MatterMail) PostFile(message string, emailname string, emailbody *string, attach *[]enmime.MIMEPart) error {

	client := model.NewClient(m.cfg.Server)

	if _, err := client.LoginByEmail(m.cfg.Team, m.cfg.MattermostUser, m.cfg.MattermostPass); err != nil {
		return err
	}

	m.logI.Println("Post new message")

	defer client.Logout()

	//Discover channel id by channel name
	var channel_id string

	rget := client.Must(client.GetChannels("")).Data.(*model.ChannelList)

	nameMatch := false
	for _, c := range rget.Channels {
		if c.Name == m.cfg.Channel {
			channel_id = c.Id
			nameMatch = true
			break
		}
	}

	if !nameMatch {
		return fmt.Errorf("Did not find channel with name %v", m.cfg.Channel)
	}

	if len(*attach) == 0 && len(emailname) == 0 {
		return m.postMessage(client, channel_id, message, nil)
	}

	buf := &bytes.Buffer{}
	writer := multipart.NewWriter(buf)

	var email []byte
	if len(emailname) > 0 {
		email = []byte(*emailbody)
		if err := addPart(client, emailname, &email, writer); err != nil {
			return err
		}
	}

	for _, a := range *attach {
		email = a.Content()
		if err := addPart(client, a.FileName(), &email, writer); err != nil {
			return err
		}
	}

	field, err := writer.CreateFormField("channel_id")
	if err != nil {
		return err
	}

	_, err = field.Write([]byte(channel_id))
	if err != nil {
		return err
	}

	err = writer.Close()
	if err != nil {
		return err
	}

	resp, err := client.UploadFile("/files/upload", buf.Bytes(), writer.FormDataContentType())
	if resp == nil {
		return err
	}

	return m.postMessage(client, channel_id, message, &resp.Data.(*model.FileUploadResponse).Filenames)
}

//Read number of lines of string
func readLines(s string, nmax int) string {
	lines := regexp.MustCompile("\r\n|\n").Split(s, nmax+1)

	if nmax < len(lines) {
		return strings.Join(lines[:nmax], "\n")
	}
	return strings.Join(lines[:], "\n")
}

//Replace cid:**** by embedded base64 image
func replaceCID(html *string, part *enmime.MIMEPart) string {
	cid := strings.Replace((*part).Header().Get("Content-ID"), "<", "", -1)
	cid = strings.Replace(cid, ">", "", -1)

	if len(cid) == 0 {
		return *html
	}

	b64 := "data:" + (*part).ContentType() + ";base64," + base64.StdEncoding.EncodeToString((*part).Content())

	return strings.Replace(*html, "cid:"+cid, b64, -1)
}

//Decode non ASCII header string RFC 1342
//encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
func NonASCII(encoded string) string {

	regex_rfc1342, _ := regexp.Compile(`=\?[^\?]*\?.\?[^\?]*\?=`)

	result := regex_rfc1342.ReplaceAllStringFunc(encoded, func(encoded string) string {
		//0 utf 1 B/Q 2 code
		v := strings.Split(encoded, "?")[1:4]
		var decoded string
		switch strings.ToLower(v[1]) {
		case "b": //Base64
			data, err := base64.StdEncoding.DecodeString(v[2])
			if err != nil {
				log.Println("Error decode Base64", err)
				return encoded
			}

			decoded = string(data)

		case "q": //Quoted-Printable
			data, err := ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(v[2])))
			if err != nil {
				log.Println("Error decode Quoted-Printable", err)
				return encoded
			}
			decoded = string(data)

		default:
			log.Println("Unknow encoding " + v[1])
			return encoded
		}

		//Decode charset
		r, err := charset.NewReader(strings.ToLower(v[0]), strings.NewReader(decoded))
		if err != nil {
			log.Println("Error decode charset", err)
			return encoded
		}

		result, _ := ioutil.ReadAll(r)

		return string(result)
	})

	return result
}

//Post an email in Mattermost
func (m *MatterMail) PostMail(msg *mail.Message) error {
	mime, _ := enmime.ParseMIMEBody(msg) // Parse message body with enmime

	var emailname, emailbody string
	if len(mime.HTML) > 0 {
		emailname = "email.html"
		emailbody = mime.HTML
		for _, p := range mime.Inlines {
			emailbody = replaceCID(&emailbody, &p)
		}

		for _, p := range mime.OtherParts {
			emailbody = replaceCID(&emailbody, &p)
		}

	} else if len(mime.Text) > 0 {
		emailname = "email.txt"
		emailbody = mime.Text
	}

	// read only some lines of text
	partmessage := readLines(mime.Text, 5)

	if partmessage != mime.Text && len(partmessage) > 0 {
		partmessage += " ..."
	}

	message := fmt.Sprintf(m.cfg.MailTemplate, NonASCII(msg.Header.Get("From")), mime.GetHeader("Subject"), partmessage)

	return m.PostFile(message, emailname, &emailbody, &mime.Attachments)
}

func InitMatterMail(cfg *config) {
	//imap.DefaultLogger = log.New(os.Stdout, "", 0)
	//imap.DefaultLogMask = imap.LogConn | imap.LogRaw

	m := &MatterMail{
		cfg:  cfg,
		logI: log.New(os.Stdout, "INFO  "+cfg.Name+"\t", log.Ltime),
		logE: log.New(os.Stderr, "ERROR "+cfg.Name+"\t", log.Ltime),
	}

	defer m.LogoutImapClient()

	m.logI.Println("Checking new emails")
	m.tryTime("Error on check new email:", m.CheckNewMails)
	m.logI.Println("Waiting new messages")

	for {
		m.tryTime("Error Idle:", m.IdleMailBox)
		m.tryTime("Error on check new email:", m.CheckNewMails)
	}
}
<|endoftext|>


<commit_before>package events_test

import (
	"github.com/30x/apid-core"
	"github.com/30x/apid-core/events"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"sync/atomic"
	"fmt"
	"strconv"
)

var _ = Describe("Events Service", func() {

	It("should ignore event with no listeners", func() {
		em := events.CreateService()
		defer em.Close()
		em.Emit("no listeners", &test_event{"test"})
	})

	It("should publish an event to a listener", func(done Done) {
		em := events.CreateService()

		h := test_handler{
			"handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				em.Close()
				close(done)
			},
		}

		em.Listen("selector", &h)
		em.Emit("selector", &test_event{"test"})
	})

	It("should publish an event to a listener func", func(done Done) {
		em := events.CreateService()

		h := func(event apid.Event) {
			defer GinkgoRecover()

			em.Close()
			close(done)
		}

		em.ListenFunc("selector", h)
		em.Emit("selector", &test_event{"test"})
	})

	It("should publish multiple events to a listener", func(done Done) {
		em := events.CreateService()

		count := int32(0)
		h := test_handler{
			"handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				c := atomic.AddInt32(&count, 1)
				if c > 1 {
					em.Close()
					close(done)
				}
			},
		}

		em.Listen("selector", &h)
		em.Emit("selector", &test_event{"test1"})
		em.Emit("selector", &test_event{"test2"})
	})

	It("EmitWithCallback should call the callback when done with delivery", func(done Done) {
		em := events.CreateService()

		delivered := func(event apid.Event) {
			defer GinkgoRecover()
			close(done)
		}

		em.EmitWithCallback("selector", &test_event{"test1"}, delivered)
	})

	It("should publish only one event to a listenOnce", func(done Done) {
		em := events.CreateService()

		count := 0
		h := func(event apid.Event) {
			defer GinkgoRecover()
			count++
		}

		delivered := func(event apid.Event) {
			defer GinkgoRecover()
			Expect(count).To(Equal(1))
			em.Close()
			close(done)
		}

		em.ListenOnceFunc("selector", h)
		em.Emit("selector", &test_event{"test1"})
		em.EmitWithCallback("selector", &test_event{"test2"}, delivered)
	})

	It("should publish an event to multiple listeners", func(done Done) {
		em := events.CreateService()
		defer em.Close()

		hitH1 := false
		hitH2 := false
		h1 := test_handler{
			"handler 1",
			func(event apid.Event) {
				defer GinkgoRecover()

				hitH1 = true
				if hitH1 && hitH2 {
					em.Close()
					close(done)
				}
			},
		}
		h2 := test_handler{
			"handler 2",
			func(event apid.Event) {
				defer GinkgoRecover()

				hitH2 = true
				if hitH1 && hitH2 {
					em.Close()
					close(done)
				}
			},
		}

		em.Listen("selector", &h1)
		em.Listen("selector", &h2)
		em.Emit("selector", &test_event{"test"})
	})

	It("should publish an event delivered event", func(done Done) {
		em := events.CreateService()
		testEvent := &test_event{"test"}
		var testSelector apid.EventSelector = "selector"

		dummy := func(event apid.Event) {}
		em.ListenFunc(testSelector, dummy)

		h := test_handler{
			"event delivered handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				e, ok := event.(apid.EventDeliveryEvent)

				Expect(ok).To(BeTrue())
				Expect(e.Event).To(Equal(testEvent))
				Expect(e.Selector).To(Equal(testSelector))

				em.Close()
				close(done)
			},
		}

		em.Listen(apid.EventDeliveredSelector, &h)
		em.Emit(testSelector, testEvent)
	})

	It("should be able to remove a listener", func(done Done) {
		em := events.CreateService()

		event1 := &test_event{"test1"}
		event2 := &test_event{"test2"}
		event3 := &test_event{"test3"}

		dummy := func(event apid.Event) {}
		em.ListenFunc("selector", dummy)

		h := test_handler{
			"handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				Expect(event).NotTo(Equal(event2))
				if event == event3 {
					em.Close()
					close(done)
				}
			},
		}
		em.Listen("selector", &h)

		// need to drive test like this because of async delivery
		td := test_handler{
			"test driver",
			func(event apid.Event) {
				defer GinkgoRecover()

				e := event.(apid.EventDeliveryEvent)
				if e.Event == event1 {
					em.StopListening("selector", &h)
					em.Emit("selector", event2)
				} else if e.Event == event2 {
					em.Listen("selector", &h)
					em.Emit("selector", event3)
				}
			},
		}
		em.Listen(apid.EventDeliveredSelector, &td)

		em.Emit("selector", event1)
	})

	It("should deliver events according selector", func(done Done) {
		em := events.CreateService()

		e1 := &test_event{"test1"}
		e2 := &test_event{"test2"}

		count := int32(0)

		h1 := test_handler{
			"handler1",
			func(event apid.Event) {
				defer GinkgoRecover()

				c := atomic.AddInt32(&count, 1)
				Expect(event).Should(Equal(e1))
				if c == 2 {
					em.Close()
					close(done)
				}
			},
		}

		h2 := test_handler{
			"handler2",
			func(event apid.Event) {
				defer GinkgoRecover()

				c := atomic.AddInt32(&count, 1)
				Expect(event).Should(Equal(e2))
				if c == 2 {
					em.Close()
					close(done)
				}
			},
		}

		em.Listen("selector1", &h1)
		em.Listen("selector2", &h2)

		em.Emit("selector2", e2)
		em.Emit("selector1", e1)
	})

	It("should publish PluginsInitialized event", func(done Done) {
		xData := make(map[string]interface{})
		xData["schemaVersion"] = "1.2.3"
		p := func(s apid.Services) (pd apid.PluginData, err error) {
			pd = apid.PluginData{
				Name: "test plugin",
				Version: "1.0.0",
				ExtraData: xData,
			}
			return
		}
		apid.RegisterPlugin(p)

		h := func(event apid.Event) {
			defer GinkgoRecover()

			if pie, ok := event.(apid.PluginsInitializedEvent); ok {

				apid.Events().Close()

				Expect(len(pie.Plugins)).Should(Equal(1))
				p := pie.Plugins[0]
				Expect(p.Name).To(Equal("test plugin"))
				Expect(p.Version).To(Equal("1.0.0"))
				Expect(p.ExtraData["schemaVersion"]).To(Equal("1.2.3"))

				close(done)
			}
		}
		apid.Events().ListenFunc(apid.SystemEventsSelector, h)

		apid.InitializePlugins()
	})

	It("shutdown event should be emitted and listened successfully", func(done Done) {
		h := func(event apid.Event) {
			defer GinkgoRecover()

			if pie, ok := event.(apid.ShutdownEvent); ok {
				apid.Events().Close()
				Expect(pie.Description).Should(Equal("apid is going to shutdown"))
				fmt.Println(pie.Description)
				close(done)
			} else {
				Fail("Received wrong event")
			}
		}
		apid.Events().ListenFunc(apid.ShutdownEventSelector, h)
		shutdownHandler := func(event apid.Event) {}
		apid.Events().EmitWithCallback(apid.ShutdownEventSelector, apid.ShutdownEvent{"apid is going to shutdown"}, shutdownHandler)
	})

	It("handlers registered by plugins should execute before apid shutdown", func(done Done) {
		pluginNum := 10
		count := int32(0)

		// create and register plugins, listen to shutdown event
		for i:=0; i<pluginNum; i++ {
			apid.RegisterPlugin(createDummyPlugin(i))
			h := func(event apid.Event) {
				if pie, ok := event.(apid.ShutdownEvent); ok {
					Expect(pie.Description).Should(Equal("apid is going to shutdown"))
					atomic.AddInt32(&count, 1)
				} else {
					Fail("Received wrong event")
				}
			}
			apid.Events().ListenFunc(apid.ShutdownEventSelector, h)
		}

		apid.InitializePlugins()

		apid.ShutdownPluginsAndWait()

		defer GinkgoRecover()
		apid.Events().Close()

		// handlers of all registered plugins have executed
		Expect(count).Should(Equal(int32(pluginNum)))

		fmt.Println("handlers of all registered plugins have executed")
		close(done)
	})
})

func createDummyPlugin(id int) apid.PluginInitFunc{
	xData := make(map[string]interface{})
	xData["schemaVersion"] = "1.2.3"
	p := func(s apid.Services) (pd apid.PluginData, err error) {
		pd = apid.PluginData{
			Name: "test plugin " + strconv.Itoa(id),
			Version: "1.0.0",
			ExtraData: xData,
		}
		return
	}
	return p

}

type test_handler struct {
	description string
	f           func(event apid.Event)
}

func (t *test_handler) String() string {
	return t.description
}

func (t *test_handler) Handle(event apid.Event) {
	t.f(event)
}

type test_event struct {
	description string
}
<commit_msg>rebuild<commit_after>package events_test

import (
	"github.com/30x/apid-core"
	"github.com/30x/apid-core/events"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"sync/atomic"
	"fmt"
	"strconv"
)

var _ = Describe("Events Service", func() {

	It("should ignore event with no listeners", func() {
		em := events.CreateService()
		defer em.Close()
		em.Emit("no listeners", &test_event{"test"})
	})

	It("should publish an event to a listener", func(done Done) {
		em := events.CreateService()

		h := test_handler{
			"handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				em.Close()
				close(done)
			},
		}

		em.Listen("selector", &h)
		em.Emit("selector", &test_event{"test"})
	})

	It("should publish an event to a listener func", func(done Done) {
		em := events.CreateService()

		h := func(event apid.Event) {
			defer GinkgoRecover()

			em.Close()
			close(done)
		}

		em.ListenFunc("selector", h)
		em.Emit("selector", &test_event{"test"})
	})

	It("should publish multiple events to a listener", func(done Done) {
		em := events.CreateService()

		count := int32(0)
		h := test_handler{
			"handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				c := atomic.AddInt32(&count, 1)
				if c > 1 {
					em.Close()
					close(done)
				}
			},
		}

		em.Listen("selector", &h)
		em.Emit("selector", &test_event{"test1"})
		em.Emit("selector", &test_event{"test2"})
	})

	It("EmitWithCallback should call the callback when done with delivery", func(done Done) {
		em := events.CreateService()

		delivered := func(event apid.Event) {
			defer GinkgoRecover()
			close(done)
		}

		em.EmitWithCallback("selector", &test_event{"test1"}, delivered)
	})

	It("should publish only one event to a listenOnce", func(done Done) {
		em := events.CreateService()

		count := 0
		h := func(event apid.Event) {
			defer GinkgoRecover()
			count++
		}

		delivered := func(event apid.Event) {
			defer GinkgoRecover()
			Expect(count).To(Equal(1))
			em.Close()
			close(done)
		}

		em.ListenOnceFunc("selector", h)
		em.Emit("selector", &test_event{"test1"})
		em.EmitWithCallback("selector", &test_event{"test2"}, delivered)
	})

	It("should publish an event to multiple listeners", func(done Done) {
		em := events.CreateService()
		defer em.Close()

		hitH1 := false
		hitH2 := false
		h1 := test_handler{
			"handler 1",
			func(event apid.Event) {
				defer GinkgoRecover()

				hitH1 = true
				if hitH1 && hitH2 {
					em.Close()
					close(done)
				}
			},
		}
		h2 := test_handler{
			"handler 2",
			func(event apid.Event) {
				defer GinkgoRecover()

				hitH2 = true
				if hitH1 && hitH2 {
					em.Close()
					close(done)
				}
			},
		}

		em.Listen("selector", &h1)
		em.Listen("selector", &h2)
		em.Emit("selector", &test_event{"test"})
	})

	It("should publish an event delivered event", func(done Done) {
		em := events.CreateService()
		testEvent := &test_event{"test"}
		var testSelector apid.EventSelector = "selector"

		dummy := func(event apid.Event) {}
		em.ListenFunc(testSelector, dummy)

		h := test_handler{
			"event delivered handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				e, ok := event.(apid.EventDeliveryEvent)

				Expect(ok).To(BeTrue())
				Expect(e.Event).To(Equal(testEvent))
				Expect(e.Selector).To(Equal(testSelector))

				em.Close()
				close(done)
			},
		}

		em.Listen(apid.EventDeliveredSelector, &h)
		em.Emit(testSelector, testEvent)
	})

	It("should be able to remove a listener", func(done Done) {
		em := events.CreateService()

		event1 := &test_event{"test1"}
		event2 := &test_event{"test2"}
		event3 := &test_event{"test3"}

		dummy := func(event apid.Event) {}
		em.ListenFunc("selector", dummy)

		h := test_handler{
			"handler",
			func(event apid.Event) {
				defer GinkgoRecover()

				Expect(event).NotTo(Equal(event2))
				if event == event3 {
					em.Close()
					close(done)
				}
			},
		}
		em.Listen("selector", &h)

		// need to drive test like this because of async delivery
		td := test_handler{
			"test driver",
			func(event apid.Event) {
				defer GinkgoRecover()

				e := event.(apid.EventDeliveryEvent)
				if e.Event == event1 {
					em.StopListening("selector", &h)
					em.Emit("selector", event2)
				} else if e.Event == event2 {
					em.Listen("selector", &h)
					em.Emit("selector", event3)
				}
			},
		}
		em.Listen(apid.EventDeliveredSelector, &td)

		em.Emit("selector", event1)
	})

	It("should deliver events according selector", func(done Done) {
		em := events.CreateService()

		e1 := &test_event{"test1"}
		e2 := &test_event{"test2"}

		count := int32(0)

		h1 := test_handler{
			"handler1",
			func(event apid.Event) {
				defer GinkgoRecover()

				c := atomic.AddInt32(&count, 1)
				Expect(event).Should(Equal(e1))
				if c == 2 {
					em.Close()
					close(done)
				}
			},
		}

		h2 := test_handler{
			"handler2",
			func(event apid.Event) {
				defer GinkgoRecover()

				c := atomic.AddInt32(&count, 1)
				Expect(event).Should(Equal(e2))
				if c == 2 {
					em.Close()
					close(done)
				}
			},
		}

		em.Listen("selector1", &h1)
		em.Listen("selector2", &h2)

		em.Emit("selector2", e2)
		em.Emit("selector1", e1)
	})

	It("should publish PluginsInitialized event", func(done Done) {
		xData := make(map[string]interface{})
		xData["schemaVersion"] = "1.2.3"
		p := func(s apid.Services) (pd apid.PluginData, err error) {
			pd = apid.PluginData{
				Name: "test plugin",
				Version: "1.0.0",
				ExtraData: xData,
			}
			return
		}
		apid.RegisterPlugin(p)

		h := func(event apid.Event) {
			defer GinkgoRecover()

			if pie, ok := event.(apid.PluginsInitializedEvent); ok {

				apid.Events().Close()

				Expect(len(pie.Plugins)).Should(Equal(1))
				p := pie.Plugins[0]
				Expect(p.Name).To(Equal("test plugin"))
				Expect(p.Version).To(Equal("1.0.0"))
				Expect(p.ExtraData["schemaVersion"]).To(Equal("1.2.3"))

				close(done)
			}
		}
		apid.Events().ListenFunc(apid.SystemEventsSelector, h)

		apid.InitializePlugins()
	})

	It("shutdown event should be emitted and listened successfully", func(done Done) {
		h := func(event apid.Event) {
			defer GinkgoRecover()

			if pie, ok := event.(apid.ShutdownEvent); ok {
				apid.Events().Close()
				Expect(pie.Description).Should(Equal("apid is going to shutdown"))
				fmt.Println(pie.Description)
				close(done)
			} else {
				Fail("Received wrong event")
			}
		}
		apid.Events().ListenFunc(apid.ShutdownEventSelector, h)
		shutdownHandler := func(event apid.Event) {}
		apid.Events().EmitWithCallback(apid.ShutdownEventSelector, apid.ShutdownEvent{"apid is going to shutdown"}, shutdownHandler)
	})

	It("handlers registered by plugins should execute before apid shutdown", func(done Done) {
		pluginNum := 10
		count := int32(0)

		// create and register plugins, listen to shutdown event
		for i:=0; i<pluginNum; i++ {
			apid.RegisterPlugin(createDummyPlugin(i))
			h := func(event apid.Event) {
				if pie, ok := event.(apid.ShutdownEvent); ok {
					Expect(pie.Description).Should(Equal("apid is going to shutdown"))
					atomic.AddInt32(&count, 1)
				} else {
					Fail("Received wrong event")
				}
			}
			apid.Events().ListenFunc(apid.ShutdownEventSelector, h)
		}

		apid.InitializePlugins()

		apid.ShutdownPluginsAndWait()

		defer GinkgoRecover()
		apid.Events().Close()

		// handlers of all registered plugins have executed
		Expect(count).Should(Equal(int32(pluginNum)))

		close(done)
	})
})

func createDummyPlugin(id int) apid.PluginInitFunc{
	xData := make(map[string]interface{})
	xData["schemaVersion"] = "1.2.3"
	p := func(s apid.Services) (pd apid.PluginData, err error) {
		pd = apid.PluginData{
			Name: "test plugin " + strconv.Itoa(id),
			Version: "1.0.0",
			ExtraData: xData,
		}
		return
	}
	return p

}

type test_handler struct {
	description string
	f           func(event apid.Event)
}

func (t *test_handler) String() string {
	return t.description
}

func (t *test_handler) Handle(event apid.Event) {
	t.f(event)
}

type test_event struct {
	description string
}
<|endoftext|>


<commit_before>/*
  Go Language Raspberry Pi Interface
  (c) Copyright David Thorpe 2016-2020
  All Rights Reserved
  For Licensing and Usage information, please see LICENSE.md
*/

package app

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"

	// Frameworks
	gopi "github.com/djthorpe/gopi/v2"
	base "github.com/djthorpe/gopi/v2/base"
)

////////////////////////////////////////////////////////////////////////////////
// INTERFACES

type server struct {
	base.App
}

////////////////////////////////////////////////////////////////////////////////
// gopi.App implementation for command-line tool

func NewServer(units ...string) (gopi.App, error) {
	this := new(server)

	// Name of the server
	name := filepath.Base(os.Args[0])

	// Append required units
	units = append(units, "server")

	// Check parameters
	if err := this.App.Init(name, units); err != nil {
		return nil, err
	}

	// Success
	return this, nil
}

func (this *server) Run() int {
	if err := this.App.Start(os.Args[1:]); err != nil {
		if errors.Is(err, flag.ErrHelp) == false {
			fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
			return -1
		} else {

		}
	}

	// Defer closing of instances to exit
	defer func() {
		if err := this.App.Close(); err != nil {
			fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
		}
	}()

	// Start server and block until done
	if server := this.UnitInstance("server").(gopi.RPCServer); server == nil {
		fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", gopi.ErrInternalAppError.WithPrefix("server"))
		return -1
	} else {
		fmt.Println("STARTING SERVER")
		if err := server.Start(); err != nil {
			fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
			return -1
		}
		fmt.Println("STOPPING SERVER")
	}

	// Success
	return 0
}
<commit_msg>Added server code for stopping<commit_after>/*
  Go Language Raspberry Pi Interface
  (c) Copyright David Thorpe 2016-2020
  All Rights Reserved
  For Licensing and Usage information, please see LICENSE.md
*/

package app

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	// Frameworks
	gopi "github.com/djthorpe/gopi/v2"
	base "github.com/djthorpe/gopi/v2/base"
)

////////////////////////////////////////////////////////////////////////////////
// INTERFACES

type server struct {
	main gopi.MainCommandFunc

	sync.WaitGroup
	base.App
}

////////////////////////////////////////////////////////////////////////////////
// gopi.App implementation for command-line tool

func NewServer(main gopi.MainCommandFunc, units ...string) (gopi.App, error) {
	this := new(server)

	// Name of the server
	name := filepath.Base(os.Args[0])

	// Append required units
	units = append(units, "server")

	// Check parameters
	if err := this.App.Init(name, units); err != nil {
		return nil, err
	} else {
		this.main = main
	}

	// Success
	return this, nil
}

func (this *server) Run() int {
	if err := this.App.Start(os.Args[1:]); err != nil {
		if errors.Is(err, flag.ErrHelp) == false {
			fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
			return -1
		} else {

		}
	}

	// Defer closing of instances to exit
	defer func() {
		if err := this.App.Close(); err != nil {
			fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
		}
	}()

	if err := this.Bus().NewHandler(gopi.EventHandler{
		Name:    "gopi.RPCEvent",
		Handler: this.RPCEventHandler,
		EventNS: gopi.EVENT_NS_DEFAULT,
	}); err != nil {
		fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
		return -1
	}

	// Start server and block until done
	if server := this.UnitInstance("server").(gopi.RPCServer); server == nil {
		fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", gopi.ErrInternalAppError.WithPrefix("server"))
		return -1
	} else {
		// Run main function in background
		this.WaitGroup.Add(1)
		go func() {
			defer this.WaitGroup.Done()
			if err := this.main(this, this.Flags().Args()); err != nil {
				fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
			}
			// Stop server gracefully
			server.Stop(false)
		}()

		fmt.Println("STARTING SERVER")
		if err := server.Start(); err != nil {
			fmt.Fprintln(os.Stderr, this.App.Flags().Name()+":", err)
			return -1
		}
		fmt.Println("STOPPING SERVER")

		// Wait for main to end
		this.WaitGroup.Wait()
	}

	// Success
	return 0
}

func (this *server) RPCEventHandler(app gopi.App, evt gopi.Event) {
	fmt.Println("handling event", evt)
}
<|endoftext|>


<commit_before>package compute

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

// FirewallRule represents a firewall rule.
type FirewallRule struct {
	ID              string            `json:"id"`
	Name            string            `json:"name"`
	Action          string            `json:"action"`
	IPVersion       string            `json:"ipVersion"`
	Protocol        string            `json:"protocol"`
	Source          FirewallRuleScope `json:"source"`
	Destination     FirewallRuleScope `json:"destination"`
	Enabled         bool              `json:"enabled"`
	State           string            `json:"state"`
	NetworkDomainID string            `json:"networkDomainId"`
	DataCenterID    string            `json:"datacenterId"`
	RuleType        string            `json:"ruleType"`
}

// GetID returns the firewall rule's Id.
func (rule *FirewallRule) GetID() string {
	return rule.ID
}

// GetName returns the firewall rule's name.
func (rule *FirewallRule) GetName() string {
	return rule.Name
}

// GetState returns the firewall rule's current state.
func (rule *FirewallRule) GetState() string {
	return rule.State
}

// IsDeleted determines whether the firewall rule has been deleted (is nil).
func (rule *FirewallRule) IsDeleted() bool {
	return rule == nil
}

var _ Resource = &FirewallRule{}

// FirewallRuleScope represents a scope (IP and / or port) for firewall configuration (source or destination).
type FirewallRuleScope struct {
	IPAddress   *FirewallRuleIPAddress `json:"ip,omitempty"`
	AddressList *EntitySummary         `json:"ipAddressList,omitempty"`
	Port        *FirewallRulePort      `json:"port,omitempty"`
}

// FirewallRuleIPAddress represents represents an IP address for firewall configuration.
type FirewallRuleIPAddress struct {
	Address    string `json:"address"`
	PrefixSize *int   `json:"PrefixSize,omitempty"`
}

// FirewallRulePort represents a firewall port configuration.
type FirewallRulePort struct {
	Begin int  `json:"begin"`
	End   *int `json:"end"`
}

// FirewallRules represents a page of FirewallRule results.
type FirewallRules struct {
	Rules []FirewallRule `json:"firewallRule"`

	PagedResult
}

const firewallMatchAny = "ANY"

// FirewallRuleConfiguration represents the configuration for a new firewall rule.
type FirewallRuleConfiguration struct {
	Name            string                `json:"name"`
	Action          string                `json:"action"`
	Enabled         bool                  `json:"enabled"`
	Placement       FirewallRulePlacement `json:"placement"`
	IPVersion       string                `json:"ipVersion"`
	Protocol        string                `json:"protocol"`
	Source          FirewallRuleScope     `json:"source"`
	Destination     FirewallRuleScope     `json:"destination"`
	NetworkDomainID string                `json:"networkDomainId"`
}

// PlaceFirst modifies the configuration so that the firewall rule will be placed in the first available position.
func (configuration *FirewallRuleConfiguration) PlaceFirst() {
	configuration.Placement = FirewallRulePlacement{
		Position: "FIRST",
	}
}

// PlaceBefore modifies the configuration so that the firewall rule will be placed before the specified rule.
func (configuration *FirewallRuleConfiguration) PlaceBefore(beforeRuleName string) {
	configuration.Placement = FirewallRulePlacement{
		Position:           "BEFORE",
		RelativeToRuleName: &beforeRuleName,
	}
}

// PlaceAfter modifies the configuration so that the firewall rule will be placed after the specified rule.
func (configuration *FirewallRuleConfiguration) PlaceAfter(afterRuleName string) {
	configuration.Placement = FirewallRulePlacement{
		Position:           "AFTER",
		RelativeToRuleName: &afterRuleName,
	}
}

// MatchAnySource modifies the configuration so that the firewall rule will match any combination of source IP and port.
func (configuration *FirewallRuleConfiguration) MatchAnySource() {
	configuration.Source = FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: firewallMatchAny,
		},
		Port: nil,
	}
}

// MatchSourceAddressAndPort modifies the configuration so that the firewall rule will match a specific source IP address (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchSourceAddressAndPort(address string, port *int) {
	sourceScope := &FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: strings.ToUpper(address),
		},
	}
	if port != nil {
		sourceScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Source = *sourceScope
}

// MatchDestinationAddressAndPort modifies the configuration so that the firewall rule will match a specific destination IP address (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchDestinationAddressAndPort(address string, port *int) {
	destinationScope := &FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: strings.ToUpper(address),
		},
	}
	if port != nil {
		destinationScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Destination = *destinationScope
}

// MatchSourceAddressListAndPort modifies the configuration so that the firewall rule will match a specific source IP address list (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchSourceAddressListAndPort(addressListID string, port *int) {
	sourceScope := &FirewallRuleScope{
		AddressList: &EntitySummary{
			ID: addressListID,
		},
	}
	if port != nil {
		sourceScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Source = *sourceScope
}

// MatchAnyDestination modifies the configuration so that the firewall rule will match any combination of destination IP and port.
func (configuration *FirewallRuleConfiguration) MatchAnyDestination() {
	configuration.Destination = FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: firewallMatchAny,
		},
		Port: nil,
	}
}

// MatchDestinationAddressListAndPort modifies the configuration so that the firewall rule will match a specific destination IP address list (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchDestinationAddressListAndPort(addressListID string, port *int) {
	destinationScope := &FirewallRuleScope{
		AddressList: &EntitySummary{
			ID: addressListID,
		},
	}
	if port != nil {
		destinationScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Destination = *destinationScope
}

// FirewallRulePlacement describes the placement for a firewall rule.
type FirewallRulePlacement struct {
	Position           string  `json:"position"`
	RelativeToRuleName *string `json:"relativeToRule,omitempty"`
}

type deleteFirewallRule struct {
	ID string `json:"id"`
}

// GetFirewallRule retrieves the Firewall rule with the specified Id.
// Returns nil if no Firewall rule is found with the specified Id.
func (client *Client) GetFirewallRule(id string) (rule *FirewallRule, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/firewallRule/%s", organizationID, id)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponse

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		if apiResponse.ResponseCode == ResponseCodeResourceNotFound {
			return nil, nil // Not an error, but was not found.
		}

		return nil, apiResponse.ToError("Request to retrieve firewall rule failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	rule = &FirewallRule{}
	err = json.Unmarshal(responseBody, rule)
	if err != nil {
		return nil, err
	}

	return rule, nil
}

// ListFirewallRules lists all firewall rules that apply to the specified network domain.
func (client *Client) ListFirewallRules(networkDomainID string) (rules *FirewallRules, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/firewallRule?networkDomainId=%s", organizationID, networkDomainID)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponse

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list firewall rules for network domain '%s' failed with status code %d (%s): %s", networkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	rules = &FirewallRules{}
	err = json.Unmarshal(responseBody, rules)

	return rules, err
}

// CreateFirewallRule creates a new firewall rule.
func (client *Client) CreateFirewallRule(configuration FirewallRuleConfiguration) (firewallRuleID string, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return "", err
	}

	log.Printf("Request = '%#v'", configuration)
	temp, _ := json.Marshal(&configuration)
	log.Printf("Request body = '%s'", string(temp))

	requestURI := fmt.Sprintf("%s/network/createFirewallRule", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &configuration)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return "", err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return "", err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return "", apiResponse.ToError("Request to create firewall rule in network domain '%s' failed with unexpected status code %d (%s): %s", configuration.NetworkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	// Expected: "info" { "name": "firewallRuleId", "value": "the-Id-of-the-new-firewall-rule" }
	if len(apiResponse.FieldMessages) != 1 || apiResponse.FieldMessages[0].FieldName != "firewallRuleId" {
		return "", apiResponse.ToError("Received an unexpected response (missing 'firewallRuleId') with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return apiResponse.FieldMessages[0].Message, nil
}

// DeleteFirewallRule deletes the specified FirewallRule rule.
func (client *Client) DeleteFirewallRule(id string) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/network/deleteFirewallRule", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost,
		&deleteFirewallRule{id},
	)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to delete firewall rule '%s' failed with unexpected status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}
<commit_msg>Implement enable / disable firewall rule.<commit_after>package compute

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

// FirewallRule represents a firewall rule.
type FirewallRule struct {
	ID              string            `json:"id"`
	Name            string            `json:"name"`
	Action          string            `json:"action"`
	IPVersion       string            `json:"ipVersion"`
	Protocol        string            `json:"protocol"`
	Source          FirewallRuleScope `json:"source"`
	Destination     FirewallRuleScope `json:"destination"`
	Enabled         bool              `json:"enabled"`
	State           string            `json:"state"`
	NetworkDomainID string            `json:"networkDomainId"`
	DataCenterID    string            `json:"datacenterId"`
	RuleType        string            `json:"ruleType"`
}

// GetID returns the firewall rule's Id.
func (rule *FirewallRule) GetID() string {
	return rule.ID
}

// GetName returns the firewall rule's name.
func (rule *FirewallRule) GetName() string {
	return rule.Name
}

// GetState returns the firewall rule's current state.
func (rule *FirewallRule) GetState() string {
	return rule.State
}

// IsDeleted determines whether the firewall rule has been deleted (is nil).
func (rule *FirewallRule) IsDeleted() bool {
	return rule == nil
}

var _ Resource = &FirewallRule{}

// FirewallRuleScope represents a scope (IP and / or port) for firewall configuration (source or destination).
type FirewallRuleScope struct {
	IPAddress   *FirewallRuleIPAddress `json:"ip,omitempty"`
	AddressList *EntitySummary         `json:"ipAddressList,omitempty"`
	Port        *FirewallRulePort      `json:"port,omitempty"`
}

// FirewallRuleIPAddress represents represents an IP address for firewall configuration.
type FirewallRuleIPAddress struct {
	Address    string `json:"address"`
	PrefixSize *int   `json:"PrefixSize,omitempty"`
}

// FirewallRulePort represents a firewall port configuration.
type FirewallRulePort struct {
	Begin int  `json:"begin"`
	End   *int `json:"end"`
}

// FirewallRules represents a page of FirewallRule results.
type FirewallRules struct {
	Rules []FirewallRule `json:"firewallRule"`

	PagedResult
}

const firewallMatchAny = "ANY"

// FirewallRuleConfiguration represents the configuration for a new firewall rule.
type FirewallRuleConfiguration struct {
	Name            string                `json:"name"`
	Action          string                `json:"action"`
	Enabled         bool                  `json:"enabled"`
	Placement       FirewallRulePlacement `json:"placement"`
	IPVersion       string                `json:"ipVersion"`
	Protocol        string                `json:"protocol"`
	Source          FirewallRuleScope     `json:"source"`
	Destination     FirewallRuleScope     `json:"destination"`
	NetworkDomainID string                `json:"networkDomainId"`
}

// PlaceFirst modifies the configuration so that the firewall rule will be placed in the first available position.
func (configuration *FirewallRuleConfiguration) PlaceFirst() {
	configuration.Placement = FirewallRulePlacement{
		Position: "FIRST",
	}
}

// PlaceBefore modifies the configuration so that the firewall rule will be placed before the specified rule.
func (configuration *FirewallRuleConfiguration) PlaceBefore(beforeRuleName string) {
	configuration.Placement = FirewallRulePlacement{
		Position:           "BEFORE",
		RelativeToRuleName: &beforeRuleName,
	}
}

// PlaceAfter modifies the configuration so that the firewall rule will be placed after the specified rule.
func (configuration *FirewallRuleConfiguration) PlaceAfter(afterRuleName string) {
	configuration.Placement = FirewallRulePlacement{
		Position:           "AFTER",
		RelativeToRuleName: &afterRuleName,
	}
}

// MatchAnySource modifies the configuration so that the firewall rule will match any combination of source IP and port.
func (configuration *FirewallRuleConfiguration) MatchAnySource() {
	configuration.Source = FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: firewallMatchAny,
		},
		Port: nil,
	}
}

// MatchSourceAddressAndPort modifies the configuration so that the firewall rule will match a specific source IP address (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchSourceAddressAndPort(address string, port *int) {
	sourceScope := &FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: strings.ToUpper(address),
		},
	}
	if port != nil {
		sourceScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Source = *sourceScope
}

// MatchDestinationAddressAndPort modifies the configuration so that the firewall rule will match a specific destination IP address (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchDestinationAddressAndPort(address string, port *int) {
	destinationScope := &FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: strings.ToUpper(address),
		},
	}
	if port != nil {
		destinationScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Destination = *destinationScope
}

// MatchSourceAddressListAndPort modifies the configuration so that the firewall rule will match a specific source IP address list (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchSourceAddressListAndPort(addressListID string, port *int) {
	sourceScope := &FirewallRuleScope{
		AddressList: &EntitySummary{
			ID: addressListID,
		},
	}
	if port != nil {
		sourceScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Source = *sourceScope
}

// MatchAnyDestination modifies the configuration so that the firewall rule will match any combination of destination IP and port.
func (configuration *FirewallRuleConfiguration) MatchAnyDestination() {
	configuration.Destination = FirewallRuleScope{
		IPAddress: &FirewallRuleIPAddress{
			Address: firewallMatchAny,
		},
		Port: nil,
	}
}

// MatchDestinationAddressListAndPort modifies the configuration so that the firewall rule will match a specific destination IP address list (and, optionall, port).
func (configuration *FirewallRuleConfiguration) MatchDestinationAddressListAndPort(addressListID string, port *int) {
	destinationScope := &FirewallRuleScope{
		AddressList: &EntitySummary{
			ID: addressListID,
		},
	}
	if port != nil {
		destinationScope.Port = &FirewallRulePort{
			Begin: *port,
		}
	}
	configuration.Destination = *destinationScope
}

// FirewallRulePlacement describes the placement for a firewall rule.
type FirewallRulePlacement struct {
	Position           string  `json:"position"`
	RelativeToRuleName *string `json:"relativeToRule,omitempty"`
}

type editFirewallRule struct {
	ID string `json:"id"`
	Enabled bool `json:"enabled"`
}

type deleteFirewallRule struct {
	ID string `json:"id"`
}

// GetFirewallRule retrieves the Firewall rule with the specified Id.
// Returns nil if no Firewall rule is found with the specified Id.
func (client *Client) GetFirewallRule(id string) (rule *FirewallRule, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/firewallRule/%s", organizationID, id)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponse

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		if apiResponse.ResponseCode == ResponseCodeResourceNotFound {
			return nil, nil // Not an error, but was not found.
		}

		return nil, apiResponse.ToError("Request to retrieve firewall rule failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	rule = &FirewallRule{}
	err = json.Unmarshal(responseBody, rule)
	if err != nil {
		return nil, err
	}

	return rule, nil
}

// ListFirewallRules lists all firewall rules that apply to the specified network domain.
func (client *Client) ListFirewallRules(networkDomainID string) (rules *FirewallRules, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/firewallRule?networkDomainId=%s", organizationID, networkDomainID)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponse

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list firewall rules for network domain '%s' failed with status code %d (%s): %s", networkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	rules = &FirewallRules{}
	err = json.Unmarshal(responseBody, rules)

	return rules, err
}

// CreateFirewallRule creates a new firewall rule.
func (client *Client) CreateFirewallRule(configuration FirewallRuleConfiguration) (firewallRuleID string, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return "", err
	}

	log.Printf("Request = '%#v'", configuration)
	temp, _ := json.Marshal(&configuration)
	log.Printf("Request body = '%s'", string(temp))

	requestURI := fmt.Sprintf("%s/network/createFirewallRule", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &configuration)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return "", err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return "", err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return "", apiResponse.ToError("Request to create firewall rule in network domain '%s' failed with unexpected status code %d (%s): %s", configuration.NetworkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	// Expected: "info" { "name": "firewallRuleId", "value": "the-Id-of-the-new-firewall-rule" }
	if len(apiResponse.FieldMessages) != 1 || apiResponse.FieldMessages[0].FieldName != "firewallRuleId" {
		return "", apiResponse.ToError("Received an unexpected response (missing 'firewallRuleId') with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return apiResponse.FieldMessages[0].Message, nil
}

// EditFirewallRule updates the configuration for a firewall rule (enable / disable).
// This operation is synchronous.
func (client *Client) EditFirewallRule(id string, enabled bool) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/network/editFirewallRule", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &editFirewallRule{
		ID:   id,
		Enabled: enabled,
	})
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to edit firewall rule failed with unexpected status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}

// DeleteFirewallRule deletes the specified FirewallRule rule.
func (client *Client) DeleteFirewallRule(id string) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/network/deleteFirewallRule", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost,
		&deleteFirewallRule{id},
	)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to delete firewall rule '%s' failed with unexpected status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}
<|endoftext|>


<commit_before>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/smtp"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/chrneumann/mimemail"
	"pkg.monsti.org/monsti/api/service"
)

type subscription struct {
}

type emitRet struct {
	Ret   []byte
	Error string
}

type signal struct {
	Name string
	Args []byte
	Ret  chan emitRet
}

type MonstiService struct {
	// Services maps service names to service paths
	Services map[string][]string
	// Mutex to syncronize data access
	mutex         sync.RWMutex
	Settings      *settings
	Logger        *log.Logger
	Handler       *nodeHandler
	subscriptions map[string][]string
	subscriber    map[string]chan *signal
	subscriberRet map[string]chan emitRet
}

type PublishServiceArgs struct {
	Service, Path string
}

func (i *MonstiService) PublishService(args PublishServiceArgs,
	reply *int) error {
	i.mutex.Lock()
	defer i.mutex.Unlock()
	if i.Services == nil {
		i.Services = make(map[string][]string)
	}
	switch args.Service {
	default:
		return fmt.Errorf("Unknown service type %v", args.Service)
	}

	if i.Services[args.Service] == nil {
		i.Services[args.Service] = make([]string, 0)
	}
	i.Services[args.Service] = append(i.Services[args.Service], args.Path)

	return nil
}

func (i *MonstiService) ModuleInitDone(args string, reply *int) error {
	// TODO Implement me
	return nil
}

func (m *MonstiService) SendMail(mail mimemail.Mail, reply *int) error {
	if !m.Settings.Mail.Debug {
		auth := smtp.PlainAuth("", m.Settings.Mail.Username,
			m.Settings.Mail.Password, strings.Split(m.Settings.Mail.Host, ":")[0])
		if err := smtp.SendMail(m.Settings.Mail.Host, auth,
			mail.Sender(), mail.Recipients(), mail.Message()); err != nil {
			return fmt.Errorf("monsti: Could not send email: %v", err)
		}
	} else {
		m.Logger.Printf(`SendMail debug:
From: %v
To: %v
Cc: %v
Bcc: %v
Subject: %v
-- Body Start --
%v
-- Body End --`,
			mail.From, mail.To, mail.Cc, mail.Bcc, mail.Subject, string(mail.Body))
	}
	return nil
}

type ConnectSignalArgs struct {
	Id, Signal string
}

func (m *MonstiService) ConnectSignal(args *ConnectSignalArgs, ret *int) error {
	if m.subscriptions == nil {
		m.subscriptions = make(map[string][]string)
		m.subscriber = make(map[string]chan *signal)
	}
	m.subscriptions[args.Signal] = append(m.subscriptions[args.Signal], args.Id)
	if _, ok := m.subscriber[args.Id]; !ok {
		m.subscriber[args.Id] = make(chan *signal)
	}
	return nil
}

type Receive struct {
	Name string
	Args []byte
}

func (m *MonstiService) EmitSignal(args *Receive, ret *[][]byte) error {
	*ret = make([][]byte, len(m.subscriptions[args.Name]))
	for i, id := range m.subscriptions[args.Name] {
		retChan := make(chan emitRet)
		done := false
		go func() {
			time.Sleep(time.Second)
			for !done {
				time.Sleep(30 * time.Second)
				m.Logger.Printf(
					"Waiting for signal response. Signal: %v, Subscriber: %v",
					args.Name, id)
			}
		}()
		m.subscriber[id] <- &signal{args.Name, args.Args, retChan}
		emitRet := <-retChan
		if len(emitRet.Error) > 0 {
			return fmt.Errorf("Received error as signal response: %v", emitRet.Error)
		}
		(*ret)[i] = emitRet.Ret
		done = true
	}
	return nil
}

type WaitSignalRet struct {
	Name string
	Args []byte
}

func (m *MonstiService) WaitSignal(subscriber string, ret *WaitSignalRet) error {
	signal := <-m.subscriber[subscriber]
	ret.Name = signal.Name
	ret.Args = signal.Args
	if m.subscriberRet == nil {
		m.subscriberRet = make(map[string]chan emitRet)
	}
	m.subscriberRet[subscriber] = signal.Ret
	return nil
}

type FinishSignalArgs struct {
	Id  string
	Err string
	Ret []byte
}

func (m *MonstiService) FinishSignal(args *FinishSignalArgs, _ *int) error {
	m.subscriberRet[args.Id] <- emitRet{args.Ret, args.Err}
	return nil
}

// getNode looks up the given node.
// If no such node exists, return nil.
// It adds a path attribute with the given path.
func getNode(root, path string) (node []byte, err error) {
	node_path := filepath.Join(root, path[1:], "node.json")
	node, err = ioutil.ReadFile(node_path)
	if os.IsNotExist(err) {
		return nil, nil
	}
	if err != nil {
		return
	}
	pathJSON := fmt.Sprintf(`{"Path":%q,`, path)
	node = bytes.Replace(node, []byte("{"), []byte(pathJSON), 1)
	return
}

// getChildren looks up child nodes of the given node.
func getChildren(root, path string) (nodes [][]byte, err error) {
	files, err := ioutil.ReadDir(filepath.Join(root, path))
	if err != nil {
		return
	}
	for _, file := range files {
		node, _ := getNode(root, filepath.Join(path, file.Name()))
		if err != nil {
			return nil, err
		}
		if node != nil {
			nodes = append(nodes, node)
		} else if file.IsDir() {
			nodes = append(nodes,
				[]byte(fmt.Sprintf(`{"Path":%q,"Type":"core.Path"}`,
					filepath.Join(path, file.Name()))))
		}
	}
	return
}

type GetChildrenArgs struct {
	Site, Path string
}

func (i *MonstiService) GetChildren(args GetChildrenArgs,
	reply *[][]byte) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	ret, err := getChildren(site, args.Path)
	*reply = ret
	return err
}

type GetNodeArgs struct{ Site, Path string }

func (i *MonstiService) GetNode(args *GetNodeDataArgs,
	reply *[]byte) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	ret, err := getNode(site, args.Path)
	*reply = ret
	return err
}

type GetNodeDataArgs struct{ Site, Path, File string }

func (i *MonstiService) GetNodeData(args *GetNodeDataArgs,
	reply *[]byte) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	path := filepath.Join(site, args.Path[1:], args.File)
	ret, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		*reply = nil
		return nil
	}
	*reply = ret
	return err
}

type WriteNodeDataArgs struct {
	Site, Path, File string
	Content          []byte
}

func (i *MonstiService) WriteNodeData(args *WriteNodeDataArgs,
	reply *int) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	path := filepath.Join(site, args.Path[1:], args.File)
	err := os.MkdirAll(filepath.Dir(path), 0700)
	if err != nil {
		return fmt.Errorf("Could not create node directory: %v", err)
	}
	err = ioutil.WriteFile(path, []byte(args.Content), 0600)
	if err != nil {
		return fmt.Errorf("Could not write node data: %v", err)
	}
	return nil
}

type RemoveNodeDataArgs struct {
	Site, Path, File string
}

func (i *MonstiService) RemoveNodeData(args *RemoveNodeDataArgs,
	reply *int) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	path := filepath.Join(site, args.Path[1:], args.File)
	if err := os.Remove(path); err != nil {
		return fmt.Errorf("Could not remove node data: %v", err)
	}
	return nil
}

type RemoveNodeArgs struct {
	Site, Node string
}

func (i *MonstiService) RemoveNode(args *RemoveNodeArgs, reply *int) error {
	root := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	nodePath := filepath.Join(root, args.Node[1:])
	if err := os.RemoveAll(nodePath); err != nil {
		return fmt.Errorf("Can't remove node: %v", err)
	}
	return nil
}

type RenameNodeArgs struct {
	Site, Source, Target string
}

func (i *MonstiService) RenameNode(args *RenameNodeArgs, reply *int) error {
	root := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	if err := os.MkdirAll(
		filepath.Dir(filepath.Join(root, args.Target)), 0700); err != nil {
		return fmt.Errorf("Can't create parent directory: %v", err)
	}
	if err := os.Rename(
		filepath.Join(root, args.Source),
		filepath.Join(root, args.Target)); err != nil {
		return fmt.Errorf("Can't move node: %v", err)
	}
	return nil
}

// getConfig returns the configuration value or section for the given name.
// If the file does not exist, it returns a nil slice.
func getConfig(path, name string) ([]byte, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("Could not read configuration: %v", err)
	}
	var target interface{}
	err = json.Unmarshal(content, &target)
	if err != nil {
		return nil, fmt.Errorf("Could not parse configuration: %v", err)
	}
	subs := strings.Split(name, ".")
	for _, sub := range subs {
		if sub == "" {
			break
		}
		targetT := reflect.TypeOf(target)
		if targetT != reflect.TypeOf(map[string]interface{}{}) {
			target = nil
			break
		}
		var ok bool
		if target, ok = (target.(map[string]interface{}))[sub]; !ok {
			target = nil
			break
		}
	}
	target = map[string]interface{}{"Value": target}
	ret, err := json.Marshal(target)
	if err != nil {
		return nil, fmt.Errorf("Could not encode configuration: %v", err)
	}
	return ret, nil
}

type GetSiteConfigArgs struct{ Site, Name string }

func (i *MonstiService) GetSiteConfig(args *GetSiteConfigArgs,
	reply *[]byte) error {
	configPath := i.Settings.Monsti.GetSiteConfigPath(args.Site)
	parts := strings.SplitN(args.Name, ".", 2)
	module := parts[0]
	name := parts[1]
	config, err := getConfig(filepath.Join(configPath, module+".json"), name)
	if err != nil {
		reply = nil
		return err
	}
	*reply = config
	return nil
}

func findAddableNodeTypes(nodeType string,
	nodeTypes map[string]*service.NodeType) []string {
	types := make([]string, 0)
	for _, otherNodeType := range nodeTypes {
		for _, addableTo := range otherNodeType.AddableTo {
			if addableTo == "." ||
				addableTo == nodeType || (addableTo[len(addableTo)-1] == '.' &&
				nodeType[0:len(addableTo)] == addableTo) {
				types = append(types, otherNodeType.Id)
				break
			}
		}
	}
	return types
}

type GetAddableNodeTypesArgs struct{ Site, NodeType string }

func (i *MonstiService) GetAddableNodeTypes(args GetAddableNodeTypesArgs,
	types *[]string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	*types = findAddableNodeTypes(args.NodeType, i.Settings.Config.NodeTypes)
	return nil
}

func (i *MonstiService) GetNodeType(nodeTypeID string,
	ret *service.NodeType) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	if nodeType, ok := i.Settings.Config.NodeTypes[nodeTypeID]; ok {
		*ret = *nodeType
		return nil
	}
	return fmt.Errorf("Unknown node type %q", nodeTypeID)
}

func (m *MonstiService) RegisterNodeType(nodeType *service.NodeType,
	reply *int) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	if _, ok := m.Settings.Config.NodeTypes[nodeType.Id]; ok {
		return fmt.Errorf("Node type with id %v does already exist", nodeType.Id)
	}
	if m.Settings.Config.NodeTypes == nil {
		m.Settings.Config.NodeTypes = make(map[string]*service.NodeType)
		m.Settings.Config.NodeFields = make(map[string]*service.NodeField)
	}
	m.Settings.Config.NodeTypes[nodeType.Id] = nodeType
	for i, field := range nodeType.Fields {
		if existing, ok := m.Settings.Config.NodeFields[field.Id]; ok {
			nodeType.Fields[i] = existing
		} else {
			m.Settings.Config.NodeFields[field.Id] = field
		}
	}
	return nil
}

func (i *MonstiService) GetRequest(id uint, req *service.Request) error {
	if r := i.Handler.GetRequest(id); r != nil {
		*req = *r
	}
	return nil
}
<commit_msg>Ignore paths in node data file names.<commit_after>// This file is part of Monsti, a web content management system.
// Copyright 2012-2013 Christian Neumann
//
// Monsti is free software: you can redistribute it and/or modify it under the
// terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// Monsti is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Monsti.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/smtp"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/chrneumann/mimemail"
	"pkg.monsti.org/monsti/api/service"
)

type subscription struct {
}

type emitRet struct {
	Ret   []byte
	Error string
}

type signal struct {
	Name string
	Args []byte
	Ret  chan emitRet
}

type MonstiService struct {
	// Services maps service names to service paths
	Services map[string][]string
	// Mutex to syncronize data access
	mutex         sync.RWMutex
	Settings      *settings
	Logger        *log.Logger
	Handler       *nodeHandler
	subscriptions map[string][]string
	subscriber    map[string]chan *signal
	subscriberRet map[string]chan emitRet
}

type PublishServiceArgs struct {
	Service, Path string
}

func (i *MonstiService) PublishService(args PublishServiceArgs,
	reply *int) error {
	i.mutex.Lock()
	defer i.mutex.Unlock()
	if i.Services == nil {
		i.Services = make(map[string][]string)
	}
	switch args.Service {
	default:
		return fmt.Errorf("Unknown service type %v", args.Service)
	}

	if i.Services[args.Service] == nil {
		i.Services[args.Service] = make([]string, 0)
	}
	i.Services[args.Service] = append(i.Services[args.Service], args.Path)

	return nil
}

func (i *MonstiService) ModuleInitDone(args string, reply *int) error {
	// TODO Implement me
	return nil
}

func (m *MonstiService) SendMail(mail mimemail.Mail, reply *int) error {
	if !m.Settings.Mail.Debug {
		auth := smtp.PlainAuth("", m.Settings.Mail.Username,
			m.Settings.Mail.Password, strings.Split(m.Settings.Mail.Host, ":")[0])
		if err := smtp.SendMail(m.Settings.Mail.Host, auth,
			mail.Sender(), mail.Recipients(), mail.Message()); err != nil {
			return fmt.Errorf("monsti: Could not send email: %v", err)
		}
	} else {
		m.Logger.Printf(`SendMail debug:
From: %v
To: %v
Cc: %v
Bcc: %v
Subject: %v
-- Body Start --
%v
-- Body End --`,
			mail.From, mail.To, mail.Cc, mail.Bcc, mail.Subject, string(mail.Body))
	}
	return nil
}

type ConnectSignalArgs struct {
	Id, Signal string
}

func (m *MonstiService) ConnectSignal(args *ConnectSignalArgs, ret *int) error {
	if m.subscriptions == nil {
		m.subscriptions = make(map[string][]string)
		m.subscriber = make(map[string]chan *signal)
	}
	m.subscriptions[args.Signal] = append(m.subscriptions[args.Signal], args.Id)
	if _, ok := m.subscriber[args.Id]; !ok {
		m.subscriber[args.Id] = make(chan *signal)
	}
	return nil
}

type Receive struct {
	Name string
	Args []byte
}

func (m *MonstiService) EmitSignal(args *Receive, ret *[][]byte) error {
	*ret = make([][]byte, len(m.subscriptions[args.Name]))
	for i, id := range m.subscriptions[args.Name] {
		retChan := make(chan emitRet)
		done := false
		go func() {
			time.Sleep(time.Second)
			for !done {
				time.Sleep(30 * time.Second)
				m.Logger.Printf(
					"Waiting for signal response. Signal: %v, Subscriber: %v",
					args.Name, id)
			}
		}()
		m.subscriber[id] <- &signal{args.Name, args.Args, retChan}
		emitRet := <-retChan
		if len(emitRet.Error) > 0 {
			return fmt.Errorf("Received error as signal response: %v", emitRet.Error)
		}
		(*ret)[i] = emitRet.Ret
		done = true
	}
	return nil
}

type WaitSignalRet struct {
	Name string
	Args []byte
}

func (m *MonstiService) WaitSignal(subscriber string, ret *WaitSignalRet) error {
	signal := <-m.subscriber[subscriber]
	ret.Name = signal.Name
	ret.Args = signal.Args
	if m.subscriberRet == nil {
		m.subscriberRet = make(map[string]chan emitRet)
	}
	m.subscriberRet[subscriber] = signal.Ret
	return nil
}

type FinishSignalArgs struct {
	Id  string
	Err string
	Ret []byte
}

func (m *MonstiService) FinishSignal(args *FinishSignalArgs, _ *int) error {
	m.subscriberRet[args.Id] <- emitRet{args.Ret, args.Err}
	return nil
}

// getNode looks up the given node.
// If no such node exists, return nil.
// It adds a path attribute with the given path.
func getNode(root, path string) (node []byte, err error) {
	node_path := filepath.Join(root, path[1:], "node.json")
	node, err = ioutil.ReadFile(node_path)
	if os.IsNotExist(err) {
		return nil, nil
	}
	if err != nil {
		return
	}
	pathJSON := fmt.Sprintf(`{"Path":%q,`, path)
	node = bytes.Replace(node, []byte("{"), []byte(pathJSON), 1)
	return
}

// getChildren looks up child nodes of the given node.
func getChildren(root, path string) (nodes [][]byte, err error) {
	files, err := ioutil.ReadDir(filepath.Join(root, path))
	if err != nil {
		return
	}
	for _, file := range files {
		node, _ := getNode(root, filepath.Join(path, file.Name()))
		if err != nil {
			return nil, err
		}
		if node != nil {
			nodes = append(nodes, node)
		} else if file.IsDir() {
			nodes = append(nodes,
				[]byte(fmt.Sprintf(`{"Path":%q,"Type":"core.Path"}`,
					filepath.Join(path, file.Name()))))
		}
	}
	return
}

type GetChildrenArgs struct {
	Site, Path string
}

func (i *MonstiService) GetChildren(args GetChildrenArgs,
	reply *[][]byte) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	ret, err := getChildren(site, args.Path)
	*reply = ret
	return err
}

type GetNodeArgs struct{ Site, Path string }

func (i *MonstiService) GetNode(args *GetNodeDataArgs,
	reply *[]byte) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	ret, err := getNode(site, args.Path)
	*reply = ret
	return err
}

type GetNodeDataArgs struct{ Site, Path, File string }

func (i *MonstiService) GetNodeData(args *GetNodeDataArgs,
	reply *[]byte) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	path := filepath.Join(site, args.Path[1:], filepath.Base(args.File))
	ret, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		*reply = nil
		return nil
	}
	*reply = ret
	return err
}

type WriteNodeDataArgs struct {
	Site, Path, File string
	Content          []byte
}

func (i *MonstiService) WriteNodeData(args *WriteNodeDataArgs,
	reply *int) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	path := filepath.Join(site, args.Path[1:], filepath.Base(args.File))
	err := os.MkdirAll(filepath.Dir(path), 0700)
	if err != nil {
		return fmt.Errorf("Could not create node directory: %v", err)
	}
	err = ioutil.WriteFile(path, []byte(args.Content), 0600)
	if err != nil {
		return fmt.Errorf("Could not write node data: %v", err)
	}
	return nil
}

type RemoveNodeDataArgs struct {
	Site, Path, File string
}

func (i *MonstiService) RemoveNodeData(args *RemoveNodeDataArgs,
	reply *int) error {
	site := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	path := filepath.Join(site, args.Path[1:], filepath.Base(args.File))
	if err := os.Remove(path); err != nil {
		return fmt.Errorf("Could not remove node data: %v", err)
	}
	return nil
}

type RemoveNodeArgs struct {
	Site, Node string
}

func (i *MonstiService) RemoveNode(args *RemoveNodeArgs, reply *int) error {
	root := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	nodePath := filepath.Join(root, args.Node[1:])
	if err := os.RemoveAll(nodePath); err != nil {
		return fmt.Errorf("Can't remove node: %v", err)
	}
	return nil
}

type RenameNodeArgs struct {
	Site, Source, Target string
}

func (i *MonstiService) RenameNode(args *RenameNodeArgs, reply *int) error {
	root := i.Settings.Monsti.GetSiteNodesPath(args.Site)
	if err := os.MkdirAll(
		filepath.Dir(filepath.Join(root, args.Target)), 0700); err != nil {
		return fmt.Errorf("Can't create parent directory: %v", err)
	}
	if err := os.Rename(
		filepath.Join(root, args.Source),
		filepath.Join(root, args.Target)); err != nil {
		return fmt.Errorf("Can't move node: %v", err)
	}
	return nil
}

// getConfig returns the configuration value or section for the given name.
// If the file does not exist, it returns a nil slice.
func getConfig(path, name string) ([]byte, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("Could not read configuration: %v", err)
	}
	var target interface{}
	err = json.Unmarshal(content, &target)
	if err != nil {
		return nil, fmt.Errorf("Could not parse configuration: %v", err)
	}
	subs := strings.Split(name, ".")
	for _, sub := range subs {
		if sub == "" {
			break
		}
		targetT := reflect.TypeOf(target)
		if targetT != reflect.TypeOf(map[string]interface{}{}) {
			target = nil
			break
		}
		var ok bool
		if target, ok = (target.(map[string]interface{}))[sub]; !ok {
			target = nil
			break
		}
	}
	target = map[string]interface{}{"Value": target}
	ret, err := json.Marshal(target)
	if err != nil {
		return nil, fmt.Errorf("Could not encode configuration: %v", err)
	}
	return ret, nil
}

type GetSiteConfigArgs struct{ Site, Name string }

func (i *MonstiService) GetSiteConfig(args *GetSiteConfigArgs,
	reply *[]byte) error {
	configPath := i.Settings.Monsti.GetSiteConfigPath(args.Site)
	parts := strings.SplitN(args.Name, ".", 2)
	module := parts[0]
	name := parts[1]
	config, err := getConfig(filepath.Join(configPath, module+".json"), name)
	if err != nil {
		reply = nil
		return err
	}
	*reply = config
	return nil
}

func findAddableNodeTypes(nodeType string,
	nodeTypes map[string]*service.NodeType) []string {
	types := make([]string, 0)
	for _, otherNodeType := range nodeTypes {
		for _, addableTo := range otherNodeType.AddableTo {
			if addableTo == "." ||
				addableTo == nodeType || (addableTo[len(addableTo)-1] == '.' &&
				nodeType[0:len(addableTo)] == addableTo) {
				types = append(types, otherNodeType.Id)
				break
			}
		}
	}
	return types
}

type GetAddableNodeTypesArgs struct{ Site, NodeType string }

func (i *MonstiService) GetAddableNodeTypes(args GetAddableNodeTypesArgs,
	types *[]string) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	*types = findAddableNodeTypes(args.NodeType, i.Settings.Config.NodeTypes)
	return nil
}

func (i *MonstiService) GetNodeType(nodeTypeID string,
	ret *service.NodeType) error {
	i.mutex.RLock()
	defer i.mutex.RUnlock()
	if nodeType, ok := i.Settings.Config.NodeTypes[nodeTypeID]; ok {
		*ret = *nodeType
		return nil
	}
	return fmt.Errorf("Unknown node type %q", nodeTypeID)
}

func (m *MonstiService) RegisterNodeType(nodeType *service.NodeType,
	reply *int) error {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	if _, ok := m.Settings.Config.NodeTypes[nodeType.Id]; ok {
		return fmt.Errorf("Node type with id %v does already exist", nodeType.Id)
	}
	if m.Settings.Config.NodeTypes == nil {
		m.Settings.Config.NodeTypes = make(map[string]*service.NodeType)
		m.Settings.Config.NodeFields = make(map[string]*service.NodeField)
	}
	m.Settings.Config.NodeTypes[nodeType.Id] = nodeType
	for i, field := range nodeType.Fields {
		if existing, ok := m.Settings.Config.NodeFields[field.Id]; ok {
			nodeType.Fields[i] = existing
		} else {
			m.Settings.Config.NodeFields[field.Id] = field
		}
	}
	return nil
}

func (i *MonstiService) GetRequest(id uint, req *service.Request) error {
	if r := i.Handler.GetRequest(id); r != nil {
		*req = *r
	}
	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/docker/engine"
	"github.com/docker/docker/pkg/parsers"
	"github.com/docker/docker/pkg/version"
	"github.com/docker/libtrust"
)

const (
	APIVERSION            version.Version = "1.18"
	DEFAULTHTTPHOST                       = "127.0.0.1"
	DEFAULTUNIXSOCKET                     = "/var/run/docker.sock"
	DefaultDockerfileName string          = "Dockerfile"
)

func ValidateHost(val string) (string, error) {
	host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
	if err != nil {
		return val, err
	}
	return host, nil
}

// TODO remove, used on < 1.5 in getContainersJSON
func DisplayablePorts(ports *engine.Table) string {
	var (
		result          = []string{}
		hostMappings    = []string{}
		firstInGroupMap map[string]int
		lastInGroupMap  map[string]int
	)
	firstInGroupMap = make(map[string]int)
	lastInGroupMap = make(map[string]int)
	ports.SetKey("PrivatePort")
	ports.Sort()
	for _, port := range ports.Data {
		var (
			current      = port.GetInt("PrivatePort")
			portKey      = port.Get("Type")
			firstInGroup int
			lastInGroup  int
		)
		if port.Get("IP") != "" {
			if port.GetInt("PublicPort") != current {
				hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
				continue
			}
			portKey = fmt.Sprintf("%s/%s", port.Get("IP"), port.Get("Type"))
		}
		firstInGroup = firstInGroupMap[portKey]
		lastInGroup = lastInGroupMap[portKey]

		if firstInGroup == 0 {
			firstInGroupMap[portKey] = current
			lastInGroupMap[portKey] = current
			continue
		}

		if current == (lastInGroup + 1) {
			lastInGroupMap[portKey] = current
			continue
		}
		result = append(result, FormGroup(portKey, firstInGroup, lastInGroup))
		firstInGroupMap[portKey] = current
		lastInGroupMap[portKey] = current
	}
	for portKey, firstInGroup := range firstInGroupMap {
		result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
	}
	result = append(result, hostMappings...)
	return strings.Join(result, ", ")
}

func FormGroup(key string, start, last int) string {
	var (
		group     string
		parts     = strings.Split(key, "/")
		groupType = parts[0]
		ip        = ""
	)
	if len(parts) > 1 {
		ip = parts[0]
		groupType = parts[1]
	}
	if start == last {
		group = fmt.Sprintf("%d", start)
	} else {
		group = fmt.Sprintf("%d-%d", start, last)
	}
	if ip != "" {
		group = fmt.Sprintf("%s:%s->%s", ip, group, group)
	}
	return fmt.Sprintf("%s/%s", group, groupType)
}

func MatchesContentType(contentType, expectedType string) bool {
	mimetype, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		log.Errorf("Error parsing media type: %s error: %v", contentType, err)
	}
	return err == nil && mimetype == expectedType
}

// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
// otherwise generates a new one
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
	err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700)
	if err != nil {
		return nil, err
	}
	trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
	if err == libtrust.ErrKeyFileDoesNotExist {
		trustKey, err = libtrust.GenerateECP256PrivateKey()
		if err != nil {
			return nil, fmt.Errorf("Error generating key: %s", err)
		}
		if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
			return nil, fmt.Errorf("Error saving key file: %s", err)
		}
	} else if err != nil {
		return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
	}
	return trustKey, nil
}
<commit_msg>Add comments to api/common constants, closes #11583<commit_after>package api

import (
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/docker/engine"
	"github.com/docker/docker/pkg/parsers"
	"github.com/docker/docker/pkg/version"
	"github.com/docker/libtrust"
)

// Common constants for daemon and client.
const (
	APIVERSION            version.Version = "1.18"                 // Current REST API version
	DEFAULTHTTPHOST                       = "127.0.0.1"            // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080
	DEFAULTUNIXSOCKET                     = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket
	DefaultDockerfileName string          = "Dockerfile"           // Default filename with Docker commands, read by docker build
)

func ValidateHost(val string) (string, error) {
	host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val)
	if err != nil {
		return val, err
	}
	return host, nil
}

// TODO remove, used on < 1.5 in getContainersJSON
func DisplayablePorts(ports *engine.Table) string {
	var (
		result          = []string{}
		hostMappings    = []string{}
		firstInGroupMap map[string]int
		lastInGroupMap  map[string]int
	)
	firstInGroupMap = make(map[string]int)
	lastInGroupMap = make(map[string]int)
	ports.SetKey("PrivatePort")
	ports.Sort()
	for _, port := range ports.Data {
		var (
			current      = port.GetInt("PrivatePort")
			portKey      = port.Get("Type")
			firstInGroup int
			lastInGroup  int
		)
		if port.Get("IP") != "" {
			if port.GetInt("PublicPort") != current {
				hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
				continue
			}
			portKey = fmt.Sprintf("%s/%s", port.Get("IP"), port.Get("Type"))
		}
		firstInGroup = firstInGroupMap[portKey]
		lastInGroup = lastInGroupMap[portKey]

		if firstInGroup == 0 {
			firstInGroupMap[portKey] = current
			lastInGroupMap[portKey] = current
			continue
		}

		if current == (lastInGroup + 1) {
			lastInGroupMap[portKey] = current
			continue
		}
		result = append(result, FormGroup(portKey, firstInGroup, lastInGroup))
		firstInGroupMap[portKey] = current
		lastInGroupMap[portKey] = current
	}
	for portKey, firstInGroup := range firstInGroupMap {
		result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
	}
	result = append(result, hostMappings...)
	return strings.Join(result, ", ")
}

func FormGroup(key string, start, last int) string {
	var (
		group     string
		parts     = strings.Split(key, "/")
		groupType = parts[0]
		ip        = ""
	)
	if len(parts) > 1 {
		ip = parts[0]
		groupType = parts[1]
	}
	if start == last {
		group = fmt.Sprintf("%d", start)
	} else {
		group = fmt.Sprintf("%d-%d", start, last)
	}
	if ip != "" {
		group = fmt.Sprintf("%s:%s->%s", ip, group, group)
	}
	return fmt.Sprintf("%s/%s", group, groupType)
}

func MatchesContentType(contentType, expectedType string) bool {
	mimetype, _, err := mime.ParseMediaType(contentType)
	if err != nil {
		log.Errorf("Error parsing media type: %s error: %v", contentType, err)
	}
	return err == nil && mimetype == expectedType
}

// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
// otherwise generates a new one
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
	err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700)
	if err != nil {
		return nil, err
	}
	trustKey, err := libtrust.LoadKeyFile(trustKeyPath)
	if err == libtrust.ErrKeyFileDoesNotExist {
		trustKey, err = libtrust.GenerateECP256PrivateKey()
		if err != nil {
			return nil, fmt.Errorf("Error generating key: %s", err)
		}
		if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
			return nil, fmt.Errorf("Error saving key file: %s", err)
		}
	} else if err != nil {
		return nil, fmt.Errorf("Error loading key file %s: %s", trustKeyPath, err)
	}
	return trustKey, nil
}
<|endoftext|>