<commit_before>package mailmessage

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"io"
	"log"
	"net/mail"
	//"net/textproto"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	CRLF               = "\r\n"
	MSG_MULTIPARTMIXED = iota
	MSG_MULTIPARTALTERNATIVE
	MSG_MESSAGE
)

type Message struct {
	Kind     int
	Header   mail.Header
	tempbody io.Reader
	Path     string
	File     *os.File
	Children []*Message
	decoded  bool
}

func (m *Message) Purge() {
	if m.File != nil {
		m.File.Close()
		m.File = nil
	}
	if len(m.Path) > 0 {
		os.Remove(m.Path)
	}
	if m.Children != nil {
		for k := range m.Children {
			m.Children[k].Purge()
		}
	}
}

func (m *Message) HTML() string {
	var rdr *os.File
	if m.Kind == MSG_MULTIPARTALTERNATIVE {
		// find html
		for k := range m.Children {
			ct := m.Children[k].Header.Get("Content-Type")
			if strings.HasPrefix(ct, "text/html") {
				rdr = m.Children[k].File
				log.Println("^^^ HTML HEADER:::", m.Children[k].Header)
				break
			}
		}
	} else if m.Kind == MSG_MESSAGE {
		ct := m.Header.Get("Content-Type")
		if strings.HasPrefix(ct, "text/html") {
			rdr = m.File
		}
	}
	if rdr == nil {
		return ""
	}
	var buffer bytes.Buffer
	io.Copy(&buffer, rdr)
	rdr.Seek(0, 0)
	return buffer.String()
}

func (m *Message) Plaintext() string {
	var rdr io.Reader
	if m.Kind == MSG_MULTIPARTALTERNATIVE {
		// find text
		for k := range m.Children {
			ct := m.Children[k].Header.Get("Content-Type")
			if strings.HasPrefix(ct, "text/plain") {
				rdr = m.Children[k].File
				break
			}
		}
	} else if m.Kind == MSG_MESSAGE {
		ct := m.Header.Get("Content-Type")
		if strings.HasPrefix(ct, "text/plain") {
			rdr = m.File
		}
	}
	if rdr == nil {
		return ""
	}
	var buffer bytes.Buffer
	io.Copy(&buffer, rdr)
	return buffer.String()
}

func New(rdr *bufio.Reader) (*Message, error) {

	line, err := rdr.ReadString('\n')
	if err != nil {
		return nil, err
	}

	if strings.HasPrefix(line, "+OK") {
		log.Println("NEW MAIL", line[4:])
	} else if strings.HasPrefix(line, "-ERR") {
		return nil, errors.New(line[5:])
	} else {
		return nil, errors.New("Unknown pop3 server response `" + line + "`")
	}
	// save to a temporary file
	nowf := "msg_" + strconv.FormatInt(time.Now().Unix(), 10) + ".dat"
	fil0, err := os.OpenFile(path.Join(os.TempDir(), nowf), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)

	if err != nil {
		return nil, err
	}

	for {
		line, err = rdr.ReadString('\n')
		if err != nil {
			return nil, err
		}
		if line == "."+CRLF {
			break
		}
		_, err = fil0.WriteString(line)
		if err != nil {
			log.Println("ERROROROR", err)
			if err.Error() == "EOF" {
				break
			}
			return nil, err
		}
	}
	fil0.Seek(0, 0)
	mainm, err := mail.ReadMessage(fil0)
	if err != nil {
		return nil, err
	}
	contentType := mainm.Header.Get("Content-Type")
	if strings.HasPrefix(contentType, "multipart/mixed") {
		return multipartMessage(mainm, fil0)
	} else if strings.HasPrefix(contentType, "multipart/alternative") {
		return alternativeMessage(mainm, fil0)
	}
	return basicMessage(mainm, fil0)
}

func basicMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	msg0 := &Message{}
	msg0.Header = mainm.Header
	msg0.File = f
	if f != nil {
		msg0.Path = f.Name()
	}
	msg0.Kind = MSG_MESSAGE
	msg0.tempbody = mainm.Body

	tf2n, tf2, _ := tempFile()
	io.Copy(tf2, msg0.tempbody)

	if f != nil {
		fn := f.Name()
		f.Close()
		os.Remove(fn)
	}
	tf2.Seek(0, 0)
	msg0.File = tf2
	msg0.Path = tf2n

	cte := msg0.Header.Get("Content-Transfer-Encoding")
	cte = strings.TrimSpace(cte)
	cte = strings.ToLower(cte)

	if cte == "base64" && !msg0.decoded {
		bio := bufio.NewReader(msg0.File)
		tf3n, tf3, _ := tempFile()
		for {
			line, err := bio.ReadString('\n')
			if err != nil {
				break
			}
			if strings.HasSuffix(line, CRLF) {
				tf3.WriteString(line[:len(line)-2])
			} else {
				tf3.WriteString(line[:len(line)-1])
			}
		}
		tf3.Sync()
		tf3.Seek(0, 0)

		trdr := base64.NewDecoder(base64.StdEncoding, tf3)
		tf2.Seek(0, 0)
		tf2.Truncate(0)
		log.Println(io.Copy(tf2, trdr))
		tf2.Sync()
		tf2.Seek(0, 0)
		tf3.Close()
		os.Remove(tf3n)
		msg0.decoded = true
	} else if cte == "quoted-printable" && !msg0.decoded {
		bio := newQuotedPrintableReader(msg0.File)
		tf3n, tf3, _ := tempFile()
		io.Copy(tf3, bio)
		tf3.Sync()
		tf3.Seek(0, 0)
		tf2.Seek(0, 0)
		tf2.Truncate(0)
		io.Copy(tf2, tf3)
		tf2.Sync()
		tf2.Seek(0, 0)
		tf3.Close()
		os.Remove(tf3n)
		msg0.decoded = true
	}

	return msg0, nil
}

func alternativeMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	a, b := multipartMessage(mainm, f)
	if b != nil {
		return nil, b
	}
	a.Kind = MSG_MULTIPARTALTERNATIVE
	return a, b
}

func multipartMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	ct := mainm.Header.Get("Content-Type")
	log.Println("it's multipart mixed", ct)
	boundary, err := getBoundary(ct)
	log.Println("`" + boundary + "`")
	if err != nil {
		log.Println("BOUNDARY ERROR", err)
		return nil, err
	}
	boundary = strings.Trim(boundary, "\"")
	rdr := bufio.NewReader(mainm.Body)
	for {
		// read first boundary
		line, lerr := rdr.ReadString('\n')
		//log.Println(line)
		if lerr != nil {
			//log.Println("FGHG ERROR", lerr)
			if lerr.Error() == "EOF" {
				break
			} else {
				return nil, lerr
			}
		}
		if strings.HasPrefix(line, "--"+boundary) {
			break
		}
	}

	msg0 := &Message{}
	msg0.File = f
	if f != nil {
		msg0.Path = f.Name()
	}
	msg0.Kind = MSG_MULTIPARTMIXED
	msg0.Header = mainm.Header
	msg0.Children = make([]*Message, 0)

	bound1 := "--" + boundary
	bound2 := "--" + boundary + "--"

	cont0 := true

	for cont0 {
		_, tf, err := tempFile()
		if err != nil {
			return nil, err
		}
		for {
			line, err := rdr.ReadString('\n')
			if err != nil {
				break
			}
			if strings.HasPrefix(line, bound2) {
				cont0 = false
				break
			} else if strings.HasPrefix(line, bound1) {
				break
			}
			tf.WriteString(line)
		}
		tf.Seek(0, 0)
		nmsg, err := mail.ReadMessage(tf)
		if err != nil {
			log.Println("nmsg, err := mail.ReadMessage(tf)", err)
			continue
		}
		var mmmsg *Message
		contentType := nmsg.Header.Get("Content-Type")
		if strings.HasPrefix(contentType, "multipart/mixed") {
			mmmsg, err = multipartMessage(nmsg, tf)
		} else if strings.HasPrefix(contentType, "multipart/alternative") {
			mmmsg, err = alternativeMessage(nmsg, tf)
		} else {
			mmmsg, err = basicMessage(nmsg, tf)
		}
		if err != nil {
			log.Println("err johnson", err)
			continue
		}
		msg0.Children = append(msg0.Children, mmmsg)
	}
	f.Close()
	os.Remove(msg0.Path)
	msg0.Path = ""
	msg0.File = nil
	return msg0, nil
}

func getBoundary(contentType string) (string, error) {
	strs := strings.Split(contentType, ";")
	for _, v := range strs {
		v = strings.TrimSpace(v)
		if strings.HasPrefix(v, "boundary=") {
			return v[9:], nil
		}
	}
	return "", errors.New("Boundary not found!")
}

var (
	tfi = 1
)

func tempFile() (string, *os.File, error) {
	tfi++
	bs := make([]byte, 6)
	rand.Read(bs)
	fn := hex.EncodeToString(bs) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + strconv.Itoa(tfi) + ".dat"
	p0 := path.Join(os.TempDir(), fn)
	file, err := os.OpenFile(p0, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
	return p0, file, err
}
<commit_msg>treat multipart/related as multipart/mixed<commit_after>package mailmessage

import (
	"bufio"
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"io"
	"log"
	"net/mail"
	//"net/textproto"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	CRLF               = "\r\n"
	MSG_MULTIPARTMIXED = iota
	MSG_MULTIPARTALTERNATIVE
	MSG_MESSAGE
)

type Message struct {
	Kind     int
	Header   mail.Header
	tempbody io.Reader
	Path     string
	File     *os.File
	Children []*Message
	decoded  bool
}

func (m *Message) Purge() {
	if m.File != nil {
		m.File.Close()
		m.File = nil
	}
	if len(m.Path) > 0 {
		os.Remove(m.Path)
	}
	if m.Children != nil {
		for k := range m.Children {
			m.Children[k].Purge()
		}
	}
}

func (m *Message) HTML() string {
	var rdr *os.File
	if m.Kind == MSG_MULTIPARTALTERNATIVE {
		// find html
		for k := range m.Children {
			ct := m.Children[k].Header.Get("Content-Type")
			if strings.HasPrefix(ct, "text/html") {
				rdr = m.Children[k].File
				log.Println("^^^ HTML HEADER:::", m.Children[k].Header)
				break
			}
		}
	} else if m.Kind == MSG_MESSAGE {
		ct := m.Header.Get("Content-Type")
		if strings.HasPrefix(ct, "text/html") {
			rdr = m.File
		}
	}
	if rdr == nil {
		return ""
	}
	var buffer bytes.Buffer
	io.Copy(&buffer, rdr)
	rdr.Seek(0, 0)
	return buffer.String()
}

func (m *Message) Plaintext() string {
	var rdr io.Reader
	if m.Kind == MSG_MULTIPARTALTERNATIVE {
		// find text
		for k := range m.Children {
			ct := m.Children[k].Header.Get("Content-Type")
			if strings.HasPrefix(ct, "text/plain") {
				rdr = m.Children[k].File
				break
			}
		}
	} else if m.Kind == MSG_MESSAGE {
		ct := m.Header.Get("Content-Type")
		if strings.HasPrefix(ct, "text/plain") {
			rdr = m.File
		}
	}
	if rdr == nil {
		return ""
	}
	var buffer bytes.Buffer
	io.Copy(&buffer, rdr)
	return buffer.String()
}

func New(rdr *bufio.Reader) (*Message, error) {

	line, err := rdr.ReadString('\n')
	if err != nil {
		return nil, err
	}

	if strings.HasPrefix(line, "+OK") {
		log.Println("NEW MAIL", line[4:])
	} else if strings.HasPrefix(line, "-ERR") {
		return nil, errors.New(line[5:])
	} else {
		return nil, errors.New("Unknown pop3 server response `" + line + "`")
	}
	// save to a temporary file
	nowf := "msg_" + strconv.FormatInt(time.Now().Unix(), 10) + ".dat"
	fil0, err := os.OpenFile(path.Join(os.TempDir(), nowf), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)

	if err != nil {
		return nil, err
	}

	for {
		line, err = rdr.ReadString('\n')
		if err != nil {
			return nil, err
		}
		if line == "."+CRLF {
			break
		}
		_, err = fil0.WriteString(line)
		if err != nil {
			log.Println("ERROROROR", err)
			if err.Error() == "EOF" {
				break
			}
			return nil, err
		}
	}
	fil0.Seek(0, 0)
	mainm, err := mail.ReadMessage(fil0)
	if err != nil {
		return nil, err
	}
	contentType := mainm.Header.Get("Content-Type")
	if strings.HasPrefix(contentType, "multipart/mixed") || strings.HasPrefix(contentType, "multipart/related") {
		return multipartMessage(mainm, fil0)
	} else if strings.HasPrefix(contentType, "multipart/alternative") {
		return alternativeMessage(mainm, fil0)
	}
	return basicMessage(mainm, fil0)
}

func basicMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	msg0 := &Message{}
	msg0.Header = mainm.Header
	msg0.File = f
	if f != nil {
		msg0.Path = f.Name()
	}
	msg0.Kind = MSG_MESSAGE
	msg0.tempbody = mainm.Body

	tf2n, tf2, _ := tempFile()
	io.Copy(tf2, msg0.tempbody)

	if f != nil {
		fn := f.Name()
		f.Close()
		os.Remove(fn)
	}
	tf2.Seek(0, 0)
	msg0.File = tf2
	msg0.Path = tf2n

	cte := msg0.Header.Get("Content-Transfer-Encoding")
	cte = strings.TrimSpace(cte)
	cte = strings.ToLower(cte)

	if cte == "base64" && !msg0.decoded {
		bio := bufio.NewReader(msg0.File)
		tf3n, tf3, _ := tempFile()
		for {
			line, err := bio.ReadString('\n')
			if err != nil {
				break
			}
			if strings.HasSuffix(line, CRLF) {
				tf3.WriteString(line[:len(line)-2])
			} else {
				tf3.WriteString(line[:len(line)-1])
			}
		}
		tf3.Sync()
		tf3.Seek(0, 0)

		trdr := base64.NewDecoder(base64.StdEncoding, tf3)
		tf2.Seek(0, 0)
		tf2.Truncate(0)
		log.Println(io.Copy(tf2, trdr))
		tf2.Sync()
		tf2.Seek(0, 0)
		tf3.Close()
		os.Remove(tf3n)
		msg0.decoded = true
	} else if cte == "quoted-printable" && !msg0.decoded {
		bio := newQuotedPrintableReader(msg0.File)
		tf3n, tf3, _ := tempFile()
		io.Copy(tf3, bio)
		tf3.Sync()
		tf3.Seek(0, 0)
		tf2.Seek(0, 0)
		tf2.Truncate(0)
		io.Copy(tf2, tf3)
		tf2.Sync()
		tf2.Seek(0, 0)
		tf3.Close()
		os.Remove(tf3n)
		msg0.decoded = true
	}

	return msg0, nil
}

func alternativeMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	a, b := multipartMessage(mainm, f)
	if b != nil {
		return nil, b
	}
	a.Kind = MSG_MULTIPARTALTERNATIVE
	return a, b
}

func multipartMessage(mainm *mail.Message, f *os.File) (*Message, error) {
	ct := mainm.Header.Get("Content-Type")
	log.Println("it's multipart mixed", ct)
	boundary, err := getBoundary(ct)
	log.Println("`" + boundary + "`")
	if err != nil {
		log.Println("BOUNDARY ERROR", err)
		return nil, err
	}
	boundary = strings.Trim(boundary, "\"")
	rdr := bufio.NewReader(mainm.Body)
	for {
		// read first boundary
		line, lerr := rdr.ReadString('\n')
		//log.Println(line)
		if lerr != nil {
			//log.Println("FGHG ERROR", lerr)
			if lerr.Error() == "EOF" {
				break
			} else {
				return nil, lerr
			}
		}
		if strings.HasPrefix(line, "--"+boundary) {
			break
		}
	}

	msg0 := &Message{}
	msg0.File = f
	if f != nil {
		msg0.Path = f.Name()
	}
	msg0.Kind = MSG_MULTIPARTMIXED
	msg0.Header = mainm.Header
	msg0.Children = make([]*Message, 0)

	bound1 := "--" + boundary
	bound2 := "--" + boundary + "--"

	cont0 := true

	for cont0 {
		_, tf, err := tempFile()
		if err != nil {
			return nil, err
		}
		for {
			line, err := rdr.ReadString('\n')
			if err != nil {
				break
			}
			if strings.HasPrefix(line, bound2) {
				cont0 = false
				break
			} else if strings.HasPrefix(line, bound1) {
				break
			}
			tf.WriteString(line)
		}
		tf.Seek(0, 0)
		nmsg, err := mail.ReadMessage(tf)
		if err != nil {
			log.Println("nmsg, err := mail.ReadMessage(tf)", err)
			continue
		}
		var mmmsg *Message
		contentType := nmsg.Header.Get("Content-Type")
		if strings.HasPrefix(contentType, "multipart/mixed") || strings.HasPrefix(contentType, "multipart/related") {
			mmmsg, err = multipartMessage(nmsg, tf)
		} else if strings.HasPrefix(contentType, "multipart/alternative") {
			mmmsg, err = alternativeMessage(nmsg, tf)
		} else {
			mmmsg, err = basicMessage(nmsg, tf)
		}
		if err != nil {
			log.Println("err johnson", err)
			continue
		}
		msg0.Children = append(msg0.Children, mmmsg)
	}
	f.Close()
	os.Remove(msg0.Path)
	msg0.Path = ""
	msg0.File = nil
	return msg0, nil
}

func getBoundary(contentType string) (string, error) {
	strs := strings.Split(contentType, ";")
	for _, v := range strs {
		v = strings.TrimSpace(v)
		if strings.HasPrefix(v, "boundary=") {
			return v[9:], nil
		}
	}
	return "", errors.New("Boundary not found!")
}

var (
	tfi = 1
)

func tempFile() (string, *os.File, error) {
	tfi++
	bs := make([]byte, 6)
	rand.Read(bs)
	fn := hex.EncodeToString(bs) + "_" + strconv.FormatInt(time.Now().Unix(), 10) + "_" + strconv.Itoa(tfi) + ".dat"
	p0 := path.Join(os.TempDir(), fn)
	file, err := os.OpenFile(p0, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
	return p0, file, err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/smartystreets/smartystreets-go-sdk/us-autocomplete-api"
	"github.com/smartystreets/smartystreets-go-sdk/wireup"
)

func main() {
	log.SetFlags(log.Ltime)

	client := wireup.NewClientBuilder().
		WithSecretKeyCredential(os.Getenv("SMARTY_AUTH_ID"), os.Getenv("SMARTY_AUTH_TOKEN")).
		WithDebugHTTPOutput(). // uncomment this line to see detailed HTTP request/response information.
		BuildUSAutocompleteAPIClient()

	lookup := &autocomplete.Lookup{Prefix: "123 main"}

	if err := client.SendLookup(lookup); err != nil {
		log.Fatal("Error sending batch:", err)
	}

	fmt.Println("Results for input:")
	fmt.Println()
	for s, suggestion := range lookup.Results {
		fmt.Println("  Suggestion:", s)
		fmt.Println(" ", suggestion.Text)
		fmt.Println(" ", suggestion.StreetLine)
		fmt.Println(" ", suggestion.City)
		fmt.Println(" ", suggestion.State)
		fmt.Println()
	}
}
<commit_msg>Commented out diagnostic output.<commit_after>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/smartystreets/smartystreets-go-sdk/us-autocomplete-api"
	"github.com/smartystreets/smartystreets-go-sdk/wireup"
)

func main() {
	log.SetFlags(log.Ltime)

	client := wireup.NewClientBuilder().
		WithSecretKeyCredential(os.Getenv("SMARTY_AUTH_ID"), os.Getenv("SMARTY_AUTH_TOKEN")).
		//WithDebugHTTPOutput(). // uncomment this line to see detailed HTTP request/response information.
		BuildUSAutocompleteAPIClient()

	lookup := &autocomplete.Lookup{Prefix: "123 main"}

	if err := client.SendLookup(lookup); err != nil {
		log.Fatal("Error sending batch:", err)
	}

	fmt.Println("Results for input:")
	fmt.Println()
	for s, suggestion := range lookup.Results {
		fmt.Println("  Suggestion:", s)
		fmt.Println(" ", suggestion.Text)
		fmt.Println(" ", suggestion.StreetLine)
		fmt.Println(" ", suggestion.City)
		fmt.Println(" ", suggestion.State)
		fmt.Println()
	}
}
<|endoftext|>


<commit_before>//Package email is designed to provide an "email interface for humans."
//Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
package email

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"os"
	"path/filepath"
	"strings"
)

const (
	MAX_LINE_LENGTH = 76 //The maximum line length per RFC 2045
)

//Email is the type used for email messages
type Email struct {
	From        string
	To          []string
	Bcc         []string
	Cc          []string
	Subject     string
	Text        string //Plaintext message (optional)
	Html        string //Html message (optional)
	Headers     textproto.MIMEHeader
	Attachments map[string]*Attachment
	ReadReceipt []string
}

//NewEmail creates an Email, and returns the pointer to it.
func NewEmail() *Email {
	return &Email{Attachments: make(map[string]*Attachment), Headers: textproto.MIMEHeader{}}
}

//Attach is used to attach a file to the email.
//It attempts to open the file referenced by filename and, if successful, creates an Attachment.
//This Attachment is then appended to the slice of Email.Attachments.
//The function will then return the Attachment for reference, as well as nil for the error, if successful.
func (e *Email) Attach(filename string) (a *Attachment, err error) {
	//Check if the file exists, return any error
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		return nil, err
	}
	//Read the file, and set the appropriate headers
	buffer, _ := ioutil.ReadFile(filename)
	e.Attachments[filename] = &Attachment{
		Filename: filename,
		Header:   textproto.MIMEHeader{},
		Content:  buffer}
	at := e.Attachments[filename]
	//Get the Content-Type to be used in the MIMEHeader
	ct := mime.TypeByExtension(filepath.Ext(filename))
	if ct != "" {
		at.Header.Set("Content-Type", ct)
	} else {
		//If the Content-Type is blank, set the Content-Type to "application/octet-stream"
		at.Header.Set("Content-Type", "application/octet-stream")
	}
	at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
	at.Header.Set("Content-Transfer-Encoding", "base64")
	return e.Attachments[filename], nil
}

//Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc.
func (e *Email) Bytes() ([]byte, error) {
	buff := &bytes.Buffer{}
	w := multipart.NewWriter(buff)
	//Set the appropriate headers (overwriting any conflicts)
	//Leave out Bcc (only included in envelope headers)
	//TODO: Support wrapping on 76 characters (ref: MIME RFC)
	e.Headers.Set("To", strings.Join(e.To, ","))
	if e.Cc != nil {
		e.Headers.Set("Cc", strings.Join(e.Cc, ","))
	}
	e.Headers.Set("From", e.From)
	e.Headers.Set("Subject", e.Subject)
	if len(e.ReadReceipt) != 0 {
		e.Headers.Set("Disposition-Notification-To", strings.Join(e.ReadReceipt, ","))
	}
	e.Headers.Set("MIME-Version", "1.0")
	e.Headers.Set("Content-Type", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))

	//Write the envelope headers (including any custom headers)
	if err := headerToBytes(buff, e.Headers); err != nil {
	}
	//Start the multipart/mixed part
	fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
	header := textproto.MIMEHeader{}
	//Check to see if there is a Text or HTML field
	if e.Text != "" || e.Html != "" {
		subWriter := multipart.NewWriter(buff)
		//Create the multipart alternative part
		header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
		//Write the header
		if err := headerToBytes(buff, header); err != nil {

		}
		//Create the body sections
		if e.Text != "" {
			header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
			/*part, err := subWriter.CreatePart(header)
			if err != nil {

			}*/
			// Write the text, splitting it into chunks of MAX_LINE_LENGTH
			//splitStr := lineSplit(e.Text)
		}
		if e.Html != "" {
			header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
			header.Set("Content-Transfer-Encoding", "quoted-printable")
			subWriter.CreatePart(header)
			// Write the text
			if err := quotePrintEncode(buff, e.Html); err != nil {

			}
		}
		subWriter.Close()
	}
	//Create attachment part, if necessary
	if e.Attachments != nil {
		for _, a := range e.Attachments {
			ap, err := w.CreatePart(a.Header)
			if err != nil {
				return nil, err
			}
			//Write the base64Wrapped content to the part
			base64Wrap(ap, a.Content)
		}
	}
	w.Close()
	return buff.Bytes(), nil
}

//Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
//This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) Send(addr string, a smtp.Auth) error {
	//Check to make sure there is at least one recipient and one "From" address
	if e.From == "" || (len(e.To) == 0 && len(e.Cc) == 0 && len(e.Bcc) == 0) {
		return errors.New("Must specify at least one From address and one To address")
	}
	// Merge the To, Cc, and Bcc fields
	to := append(append(e.To, e.Cc...), e.Bcc...)
	from, err := mail.ParseAddress(e.From)
	if err != nil {
		return err
	}
	raw, err := e.Bytes()
	if err != nil {
		return err
	}
	return smtp.SendMail(addr, a, from.Address, to, raw)
}

//Attachment is a struct representing an email attachment.
//Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
type Attachment struct {
	Filename string
	Header   textproto.MIMEHeader
	Content  []byte
}

//lineSplit splits the given string into lines of 76 characters at the most
/*func lineSplit(s string) string {
	for i, c := range s {

	}
	return ""
}*/

//quotePrintEncode writes the quoted-printable text to the IO Writer
func quotePrintEncode(w io.Writer, s string) error {
	// Basic rules (comments to be removed once this function is fully implemented)
	// * If character is printable, it can be represented AS IS
	// * Lines must have a max of 76 characters
	// * Lines must not end with whitespace
	//		- Rather, append a soft break (=) to the end of the line after the space for preservation
	// *
	_, err := fmt.Fprintf(w, "%s\r\n", s)
	//Split into MAX_LINE_LENGTH chunks, with needed soft breaks
	for i := 0; i < MAX_LINE_LENGTH; i++ {

	}
	if err != nil {
		return err
	}
	return nil
}

//base64Wrap encodeds the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
//The output is then written to the specified io.Writer
func base64Wrap(w io.Writer, b []byte) {
	encoded := base64.StdEncoding.EncodeToString(b)
	for i := 0; i < len(encoded); i += 76 {
		//Do we need to print 76 characters, or the rest of the string?
		if len(encoded)-i < 76 {
			fmt.Fprintf(w, "%s\r\n", encoded[i:])
		} else {
			fmt.Fprintf(w, "%s\r\n", encoded[i:i+76])
		}
	}
}

//headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
func headerToBytes(w io.Writer, t textproto.MIMEHeader) error {
	for k, v := range t {
		//Write the header key
		_, err := fmt.Fprintf(w, "%s:", k)
		if err != nil {
			return err
		}
		//Write each value in the header
		for _, c := range v {
			_, err := fmt.Fprintf(w, " %s\r\n", c)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>Got line wrapping working in quotePrintEncode - just need to implement special char encoding.<commit_after>//Package email is designed to provide an "email interface for humans."
//Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
package email

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"mime/multipart"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"os"
	"path/filepath"
	"strings"
)

const (
	MAX_LINE_LENGTH = 76 //The maximum line length per RFC 2045
)

//Email is the type used for email messages
type Email struct {
	From        string
	To          []string
	Bcc         []string
	Cc          []string
	Subject     string
	Text        string //Plaintext message (optional)
	Html        string //Html message (optional)
	Headers     textproto.MIMEHeader
	Attachments map[string]*Attachment
	ReadReceipt []string
}

//NewEmail creates an Email, and returns the pointer to it.
func NewEmail() *Email {
	return &Email{Attachments: make(map[string]*Attachment), Headers: textproto.MIMEHeader{}}
}

//Attach is used to attach a file to the email.
//It attempts to open the file referenced by filename and, if successful, creates an Attachment.
//This Attachment is then appended to the slice of Email.Attachments.
//The function will then return the Attachment for reference, as well as nil for the error, if successful.
func (e *Email) Attach(filename string) (a *Attachment, err error) {
	//Check if the file exists, return any error
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		return nil, err
	}
	//Read the file, and set the appropriate headers
	buffer, _ := ioutil.ReadFile(filename)
	e.Attachments[filename] = &Attachment{
		Filename: filename,
		Header:   textproto.MIMEHeader{},
		Content:  buffer}
	at := e.Attachments[filename]
	//Get the Content-Type to be used in the MIMEHeader
	ct := mime.TypeByExtension(filepath.Ext(filename))
	if ct != "" {
		at.Header.Set("Content-Type", ct)
	} else {
		//If the Content-Type is blank, set the Content-Type to "application/octet-stream"
		at.Header.Set("Content-Type", "application/octet-stream")
	}
	at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
	at.Header.Set("Content-Transfer-Encoding", "base64")
	return e.Attachments[filename], nil
}

//Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc.
func (e *Email) Bytes() ([]byte, error) {
	buff := &bytes.Buffer{}
	w := multipart.NewWriter(buff)
	//Set the appropriate headers (overwriting any conflicts)
	//Leave out Bcc (only included in envelope headers)
	//TODO: Support wrapping on 76 characters (ref: MIME RFC)
	e.Headers.Set("To", strings.Join(e.To, ","))
	if e.Cc != nil {
		e.Headers.Set("Cc", strings.Join(e.Cc, ","))
	}
	e.Headers.Set("From", e.From)
	e.Headers.Set("Subject", e.Subject)
	if len(e.ReadReceipt) != 0 {
		e.Headers.Set("Disposition-Notification-To", strings.Join(e.ReadReceipt, ","))
	}
	e.Headers.Set("MIME-Version", "1.0")
	e.Headers.Set("Content-Type", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))

	//Write the envelope headers (including any custom headers)
	if err := headerToBytes(buff, e.Headers); err != nil {
	}
	//Start the multipart/mixed part
	fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
	header := textproto.MIMEHeader{}
	//Check to see if there is a Text or HTML field
	if e.Text != "" || e.Html != "" {
		subWriter := multipart.NewWriter(buff)
		//Create the multipart alternative part
		header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
		//Write the header
		if err := headerToBytes(buff, header); err != nil {

		}
		//Create the body sections
		if e.Text != "" {
			header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
			/*part, err := subWriter.CreatePart(header)
			if err != nil {

			}*/
			// Write the text, splitting it into chunks of MAX_LINE_LENGTH
			//splitStr := lineSplit(e.Text)
		}
		if e.Html != "" {
			header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
			header.Set("Content-Transfer-Encoding", "quoted-printable")
			subWriter.CreatePart(header)
			// Write the text
			if err := quotePrintEncode(buff, e.Html); err != nil {

			}
		}
		subWriter.Close()
	}
	//Create attachment part, if necessary
	if e.Attachments != nil {
		for _, a := range e.Attachments {
			ap, err := w.CreatePart(a.Header)
			if err != nil {
				return nil, err
			}
			//Write the base64Wrapped content to the part
			base64Wrap(ap, a.Content)
		}
	}
	w.Close()
	return buff.Bytes(), nil
}

//Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
//This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) Send(addr string, a smtp.Auth) error {
	//Check to make sure there is at least one recipient and one "From" address
	if e.From == "" || (len(e.To) == 0 && len(e.Cc) == 0 && len(e.Bcc) == 0) {
		return errors.New("Must specify at least one From address and one To address")
	}
	// Merge the To, Cc, and Bcc fields
	to := append(append(e.To, e.Cc...), e.Bcc...)
	from, err := mail.ParseAddress(e.From)
	if err != nil {
		return err
	}
	raw, err := e.Bytes()
	if err != nil {
		return err
	}
	return smtp.SendMail(addr, a, from.Address, to, raw)
}

//Attachment is a struct representing an email attachment.
//Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
type Attachment struct {
	Filename string
	Header   textproto.MIMEHeader
	Content  []byte
}

//quotePrintEncode writes the quoted-printable text to the IO Writer
func quotePrintEncode(w io.Writer, s string) error {
	mc := 0
	for _, c := range s {
		//Change these to a switch
		// If we've reached the line length limit
		// or we've found a special character whose encoding will surpass the limit
		//append a soft break
		if mc == 75 || (!((c >= '!' && c <= '<') || (c >= '>' && c <= '~') || (c == ' ' || c == '\n' || c == '\t')) && mc >= 72) {
			if _, err := fmt.Fprintf(w, "%s%s", "=\r\n", string(c)); err != nil {
				return err
			}
			mc = 0
			continue
		}
		//append the appropriate character
		if (c >= '!' && c <= '<') || (c >= '>' && c <= '~') || (c == ' ' || c == '\n' || c == '\t') {
			//Printable character
			if _, err := fmt.Fprintf(w, "%s", string(c)); err != nil {
				return err
			}
			// Reset the counter if we wrote a newline
			if c == '\n' {
				mc = 0
			}
			mc++
			continue
		} else {
			//non-printable.. encode it (TODO)
			if _, err := fmt.Fprintf(w, "%s", string(c)); err != nil {
				return err
			}
			mc++
		}
	}
	return nil
}

//base64Wrap encodeds the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
//The output is then written to the specified io.Writer
func base64Wrap(w io.Writer, b []byte) {
	encoded := base64.StdEncoding.EncodeToString(b)
	for i := 0; i < len(encoded); i += 76 {
		//Do we need to print 76 characters, or the rest of the string?
		if len(encoded)-i < 76 {
			fmt.Fprintf(w, "%s\r\n", encoded[i:])
		} else {
			fmt.Fprintf(w, "%s\r\n", encoded[i:i+76])
		}
	}
}

//headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
func headerToBytes(w io.Writer, t textproto.MIMEHeader) error {
	for k, v := range t {
		//Write the header key
		_, err := fmt.Fprintf(w, "%s:", k)
		if err != nil {
			return err
		}
		//Write each value in the header
		for _, c := range v {
			_, err := fmt.Fprintf(w, " %s\r\n", c)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package mockdb

import (
	"database/sql"
	"fmt"

	"github.com/Nivl/go-rest-tools/storage/db"
	"github.com/lib/pq"
	"github.com/stretchr/testify/mock"
)

var (
	// StringType represent a string argument
	StringType  = mock.AnythingOfType("string")
	serverError = &pq.Error{
		Code:    "08006",
		Message: "error: connection failure",
		Detail:  "the connection to the database failed",
	}
)

func newConflictError(fieldName string) *pq.Error {
	return &pq.Error{
		Code:    db.ErrDup,
		Message: "error: duplicate field",
		Detail:  fmt.Sprintf("Key (%s)=(Google) already exists.", fieldName),
	}
}

// ExpectGet is a helper that expects a Get
func (mdb *DB) ExpectGet(typ string, runnable func(args mock.Arguments)) *mock.Call {
	getCall := mdb.On("Get", mock.AnythingOfType(typ), StringType, StringType)
	getCall.Return(nil)
	if runnable != nil {
		getCall.Run(runnable)
	}
	return getCall
}

// ExpectGetNotFound is a helper that expects a not found on a Get
func (mdb *DB) ExpectGetNotFound(typ string) *mock.Call {
	getCall := mdb.On("Get", mock.AnythingOfType(typ), StringType, StringType)
	getCall.Return(sql.ErrNoRows)
	return getCall
}

// ExpectDeletion is a helper that expects a deletion
func (mdb *DB) ExpectDeletion() *mock.Call {
	return mdb.On("Exec", StringType, StringType).Return(nil, nil)
}

// ExpectDeletionError is a helper that expects a deletion to fail
func (mdb *DB) ExpectDeletionError() *mock.Call {
	return mdb.On("Exec", StringType, StringType).Return(nil, serverError)
}

// ExpectInsert is a helper that expects an insertion
func (mdb *DB) ExpectInsert(typ string) *mock.Call {
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, nil)
}

// ExpectInsertError is a helper that expects an insert to fail
func (mdb *DB) ExpectInsertError() *mock.Call {
	return mdb.On("NamedExec", StringType, StringType).Return(nil, serverError)
}

// ExpectInsertConflict is a helper that expects a conflict on an insertion
func (mdb *DB) ExpectInsertConflict(typ string, fieldName string) *mock.Call {
	conflictError := newConflictError(fieldName)
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, conflictError)
}

// ExpectUpdate is a helper that expects an update
func (mdb *DB) ExpectUpdate(typ string) *mock.Call {
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, nil)
}

// ExpectUpdateConflict is a helper that expects a conflict on an update
func (mdb *DB) ExpectUpdateConflict(typ string, fieldName string) *mock.Call {
	conflictError := newConflictError(fieldName)
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, conflictError)
}

// ExpectUpdateError is a helper that expects an update to fail
func (mdb *DB) ExpectUpdateError() *mock.Call {
	return mdb.On("NamedExec", StringType, StringType).Return(nil, serverError)
}
<commit_msg>fix(mockdb): fix type issue<commit_after>package mockdb

import (
	"database/sql"
	"fmt"

	"github.com/Nivl/go-rest-tools/storage/db"
	"github.com/lib/pq"
	"github.com/stretchr/testify/mock"
)

var (
	// StringType represent a string argument
	StringType  = mock.AnythingOfType("string")
	serverError = &pq.Error{
		Code:    "08006",
		Message: "error: connection failure",
		Detail:  "the connection to the database failed",
	}
)

func newConflictError(fieldName string) *pq.Error {
	return &pq.Error{
		Code:    db.ErrDup,
		Message: "error: duplicate field",
		Detail:  fmt.Sprintf("Key (%s)=(Google) already exists.", fieldName),
	}
}

// ExpectGet is a helper that expects a Get
func (mdb *DB) ExpectGet(typ string, runnable func(args mock.Arguments)) *mock.Call {
	getCall := mdb.On("Get", mock.AnythingOfType(typ), StringType, StringType)
	getCall.Return(nil)
	if runnable != nil {
		getCall.Run(runnable)
	}
	return getCall
}

// ExpectGetNotFound is a helper that expects a not found on a Get
func (mdb *DB) ExpectGetNotFound(typ string) *mock.Call {
	getCall := mdb.On("Get", mock.AnythingOfType(typ), StringType, StringType)
	getCall.Return(sql.ErrNoRows)
	return getCall
}

// ExpectDeletion is a helper that expects a deletion
func (mdb *DB) ExpectDeletion() *mock.Call {
	return mdb.On("Exec", StringType, StringType).Return(nil, nil)
}

// ExpectDeletionError is a helper that expects a deletion to fail
func (mdb *DB) ExpectDeletionError() *mock.Call {
	return mdb.On("Exec", StringType, StringType).Return(nil, serverError)
}

// ExpectInsert is a helper that expects an insertion
func (mdb *DB) ExpectInsert(typ string) *mock.Call {
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, nil)
}

// ExpectInsertError is a helper that expects an insert to fail
func (mdb *DB) ExpectInsertError(typ string) *mock.Call {
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, serverError)
}

// ExpectInsertConflict is a helper that expects a conflict on an insertion
func (mdb *DB) ExpectInsertConflict(typ string, fieldName string) *mock.Call {
	conflictError := newConflictError(fieldName)
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, conflictError)
}

// ExpectUpdate is a helper that expects an update
func (mdb *DB) ExpectUpdate(typ string) *mock.Call {
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, nil)
}

// ExpectUpdateConflict is a helper that expects a conflict on an update
func (mdb *DB) ExpectUpdateConflict(typ string, fieldName string) *mock.Call {
	conflictError := newConflictError(fieldName)
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, conflictError)
}

// ExpectUpdateError is a helper that expects an update to fail
func (mdb *DB) ExpectUpdateError(typ string) *mock.Call {
	return mdb.On("NamedExec", StringType, mock.AnythingOfType(typ)).Return(nil, serverError)
}
<|endoftext|>


<commit_before>package backend_test

import (
	"errors"
	"testing"

	"code.cloudfoundry.org/garden"
	"github.com/concourse/concourse/worker/backend"
	"github.com/concourse/concourse/worker/backend/backendfakes"
	"github.com/concourse/concourse/worker/backend/libcontainerd/libcontainerdfakes"
	"github.com/containerd/containerd"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type BackendSuite struct {
	suite.Suite
	*require.Assertions

	backend backend.Backend
	client  *libcontainerdfakes.FakeClient
	network *backendfakes.FakeNetwork
	userns  *backendfakes.FakeUserNamespace
	killer  *backendfakes.FakeKiller
}

func (s *BackendSuite) SetupTest() {
	s.client = new(libcontainerdfakes.FakeClient)
	s.killer = new(backendfakes.FakeKiller)
	s.network = new(backendfakes.FakeNetwork)
	s.userns = new(backendfakes.FakeUserNamespace)

	var err error
	s.backend, err = backend.New(s.client,
		backend.WithKiller(s.killer),
		backend.WithNetwork(s.network),
		backend.WithUserNamespace(s.userns),
	)
	s.NoError(err)
}

func (s *BackendSuite) TestNew() {
	_, err := backend.New(nil)
	s.EqualError(err, "nil client")
}

func (s *BackendSuite) TestPing() {
	for _, tc := range []struct {
		desc          string
		versionReturn error
		succeeds      bool
	}{
		{
			desc:          "fail from containerd version service",
			succeeds:      true,
			versionReturn: nil,
		},
		{
			desc:          "ok from containerd's version service",
			succeeds:      false,
			versionReturn: errors.New("error returning version"),
		},
	} {
		s.T().Run(tc.desc, func(t *testing.T) {
			s.client.VersionReturns(tc.versionReturn)

			err := s.backend.Ping()
			if tc.succeeds {
				s.NoError(err)
				return
			}

			s.EqualError(errors.Unwrap(err), "error returning version")
		})
	}
}

var (
	invalidGdnSpec      = garden.ContainerSpec{}
	minimumValidGdnSpec = garden.ContainerSpec{
		Handle: "handle", RootFSPath: "raw:///rootfs",
	}
)

func (s *BackendSuite) TestCreateWithInvalidSpec() {
	_, err := s.backend.Create(invalidGdnSpec)

	s.Error(err)
	s.Equal(0, s.client.NewContainerCallCount())
}

func (s *BackendSuite) TestCreateWithNewContainerFailure() {
	s.client.NewContainerReturns(nil, errors.New("err"))

	_, err := s.backend.Create(minimumValidGdnSpec)
	s.Error(err)

	s.Equal(1, s.client.NewContainerCallCount())
}

func (s *BackendSuite) TestCreateContainerNewTaskFailure() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	expectedErr := errors.New("task-err")
	fakeContainer.NewTaskReturns(nil, expectedErr)

	s.client.NewContainerReturns(fakeContainer, nil)

	_, err := s.backend.Create(minimumValidGdnSpec)
	s.EqualError(errors.Unwrap(err), expectedErr.Error())

	s.Equal(1, fakeContainer.NewTaskCallCount())
}

func (s *BackendSuite) TestCreateContainerTaskStartFailure() {
	fakeTask := new(libcontainerdfakes.FakeTask)
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	s.client.NewContainerReturns(fakeContainer, nil)
	fakeContainer.NewTaskReturns(fakeTask, nil)
	fakeTask.StartReturns(errors.New("start-err"))

	_, err := s.backend.Create(minimumValidGdnSpec)
	s.Error(err)

	s.EqualError(errors.Unwrap(err), "start-err")
}

func (s *BackendSuite) TestCreateContainerSetsHandle() {
	fakeTask := new(libcontainerdfakes.FakeTask)
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	fakeContainer.IDReturns("handle")
	fakeContainer.NewTaskReturns(fakeTask, nil)

	s.client.NewContainerReturns(fakeContainer, nil)
	cont, err := s.backend.Create(minimumValidGdnSpec)
	s.NoError(err)

	s.Equal("handle", cont.Handle())

}

func (s *BackendSuite) TestContainersWithContainerdFailure() {
	s.client.ContainersReturns(nil, errors.New("err"))

	_, err := s.backend.Containers(nil)
	s.Error(err)
	s.Equal(1, s.client.ContainersCallCount())
}

func (s *BackendSuite) TestContainersWithInvalidPropertyFilters() {
	for _, tc := range []struct {
		desc   string
		filter map[string]string
	}{
		{
			desc: "empty key",
			filter: map[string]string{
				"": "bar",
			},
		},
		{
			desc: "empty value",
			filter: map[string]string{
				"foo": "",
			},
		},
	} {
		s.T().Run(tc.desc, func(t *testing.T) {
			_, err := s.backend.Containers(tc.filter)

			s.Error(err)
			s.Equal(0, s.client.ContainersCallCount())
		})
	}
}

func (s *BackendSuite) TestContainersWithProperProperties() {
	_, _ = s.backend.Containers(map[string]string{"foo": "bar", "caz": "zaz"})
	s.Equal(1, s.client.ContainersCallCount())

	_, labelSet := s.client.ContainersArgsForCall(0)
	s.ElementsMatch([]string{"labels.foo==bar", "labels.caz==zaz"}, labelSet)
}

func (s *BackendSuite) TestContainersConversion() {
	fakeContainer1 := new(libcontainerdfakes.FakeContainer)
	fakeContainer2 := new(libcontainerdfakes.FakeContainer)

	s.client.ContainersReturns([]containerd.Container{
		fakeContainer1, fakeContainer2,
	}, nil)

	containers, err := s.backend.Containers(nil)
	s.NoError(err)
	s.Equal(1, s.client.ContainersCallCount())
	s.Len(containers, 2)
}

func (s *BackendSuite) TestLookupEmptyHandleError() {
	_, err := s.backend.Lookup("")
	s.Equal("empty handle", err.Error())
}

func (s *BackendSuite) TestLookupCallGetContainerWithHandle() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeContainer.IDReturns("handle")
	s.client.GetContainerReturns(fakeContainer, nil)

	_, _ = s.backend.Lookup("handle")
	s.Equal(1, s.client.GetContainerCallCount())

	_, handle := s.client.GetContainerArgsForCall(0)
	s.Equal("handle", handle)
}

func (s *BackendSuite) TestLookupGetContainerError() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeContainer.IDReturns("handle")
	s.client.GetContainerReturns(fakeContainer, nil)

	s.client.GetContainerReturns(nil, errors.New("containerd-err"))

	_, err := s.backend.Lookup("handle")
	s.Error(err)
	s.EqualError(errors.Unwrap(err), "containerd-err")
}

func (s *BackendSuite) TestLookupGetContainerFails() {
	s.client.GetContainerReturns(nil, errors.New("err"))
	_, err := s.backend.Lookup("non-existent-handle")
	s.Error(err)
	s.EqualError(errors.Unwrap(err), "err")
}

func (s *BackendSuite) TestLookupGetNoContainerReturned() {
	s.client.GetContainerReturns(nil, errors.New("not found"))
	container, err := s.backend.Lookup("non-existent-handle")
	s.Error(err)
	s.Nil(container)
}

func (s *BackendSuite) TestLookupGetContainer() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeContainer.IDReturns("handle")
	s.client.GetContainerReturns(fakeContainer, nil)
	container, err := s.backend.Lookup("handle")
	s.NoError(err)
	s.NotNil(container)
	s.Equal("handle", container.Handle())
}

func (s *BackendSuite) TestDestroyEmptyHandleError() {
	err := s.backend.Destroy("")
	s.EqualError(err, "empty handle")
}

func (s *BackendSuite) TestDestroyGetContainerError() {
	s.client.GetContainerReturns(nil, errors.New("get-container-failed"))

	err := s.backend.Destroy("some-handle")
	s.EqualError(errors.Unwrap(err), "get-container-failed")
}

// func (s *BackendSuite) TestDestroyGracefullyStopErrors() {
// 	fakeContainer := new(libcontainerdfakes.FakeContainer)

// 	s.client.GetContainerReturns(fakeContainer, nil)
// 	s.containerStopper.GracefullyStopReturns(errors.New("gracefully-stop-failed"))

// 	err := s.backend.Destroy("some-handle")

// 	s.Equal(1, s.containerStopper.GracefullyStopCallCount())
// 	s.EqualError(errors.Unwrap(err), "gracefully-stop-failed")
// }

// func (s *BackendSuite) TestDestroyContainerDeleteError() {
// 	fakeContainer := new(libcontainerdfakes.FakeContainer)
// 	fakeContainer.DeleteReturns(errors.New("destroy-error"))

// 	s.client.GetContainerReturns(fakeContainer, nil)

// 	err := s.backend.Destroy("some-handle")

// 	s.Equal(1, s.containerStopper.GracefullyStopCallCount())
// 	s.Equal(1, fakeContainer.DeleteCallCount())
// 	s.EqualError(errors.Unwrap(err), "destroy-error")
// }

// func (s *BackendSuite) TestDestroy() {
// 	fakeContainer := new(libcontainerdfakes.FakeContainer)

// 	s.client.GetContainerReturns(fakeContainer, nil)

// 	err := s.backend.Destroy("some-handle")
// 	s.NoError(err)

// 	s.Equal(1, s.client.GetContainerCallCount())
// 	s.Equal(1, s.containerStopper.GracefullyStopCallCount())
// 	s.Equal(1, fakeContainer.DeleteCallCount())
// }

func (s *BackendSuite) TestStart() {
	err := s.backend.Start()
	s.NoError(err)
	s.Equal(1, s.client.InitCallCount())
}

func (s *BackendSuite) TestStartInitError() {
	s.client.InitReturns(errors.New("init failed"))
	err := s.backend.Start()
	s.EqualError(errors.Unwrap(err), "init failed")
}

func (s *BackendSuite) TestStop() {
	s.backend.Stop()
	s.Equal(1, s.client.StopCallCount())
}
<commit_msg>containerd: backfill tests<commit_after>package backend_test

import (
	"errors"
	"testing"

	"code.cloudfoundry.org/garden"
	"github.com/concourse/concourse/worker/backend"
	"github.com/concourse/concourse/worker/backend/backendfakes"
	"github.com/concourse/concourse/worker/backend/libcontainerd/libcontainerdfakes"
	"github.com/containerd/containerd"
	"github.com/containerd/containerd/errdefs"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type BackendSuite struct {
	suite.Suite
	*require.Assertions

	backend backend.Backend
	client  *libcontainerdfakes.FakeClient
	network *backendfakes.FakeNetwork
	userns  *backendfakes.FakeUserNamespace
	killer  *backendfakes.FakeKiller
}

func (s *BackendSuite) SetupTest() {
	s.client = new(libcontainerdfakes.FakeClient)
	s.killer = new(backendfakes.FakeKiller)
	s.network = new(backendfakes.FakeNetwork)
	s.userns = new(backendfakes.FakeUserNamespace)

	var err error
	s.backend, err = backend.New(s.client,
		backend.WithKiller(s.killer),
		backend.WithNetwork(s.network),
		backend.WithUserNamespace(s.userns),
	)
	s.NoError(err)
}

func (s *BackendSuite) TestNew() {
	_, err := backend.New(nil)
	s.EqualError(err, "nil client")
}

func (s *BackendSuite) TestPing() {
	for _, tc := range []struct {
		desc          string
		versionReturn error
		succeeds      bool
	}{
		{
			desc:          "fail from containerd version service",
			succeeds:      true,
			versionReturn: nil,
		},
		{
			desc:          "ok from containerd's version service",
			succeeds:      false,
			versionReturn: errors.New("error returning version"),
		},
	} {
		s.T().Run(tc.desc, func(t *testing.T) {
			s.client.VersionReturns(tc.versionReturn)

			err := s.backend.Ping()
			if tc.succeeds {
				s.NoError(err)
				return
			}

			s.EqualError(errors.Unwrap(err), "error returning version")
		})
	}
}

var (
	invalidGdnSpec      = garden.ContainerSpec{}
	minimumValidGdnSpec = garden.ContainerSpec{
		Handle: "handle", RootFSPath: "raw:///rootfs",
	}
)

func (s *BackendSuite) TestCreateWithInvalidSpec() {
	_, err := s.backend.Create(invalidGdnSpec)

	s.Error(err)
	s.Equal(0, s.client.NewContainerCallCount())
}

func (s *BackendSuite) TestCreateWithNewContainerFailure() {
	s.client.NewContainerReturns(nil, errors.New("err"))

	_, err := s.backend.Create(minimumValidGdnSpec)
	s.Error(err)

	s.Equal(1, s.client.NewContainerCallCount())
}

func (s *BackendSuite) TestCreateContainerNewTaskFailure() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	expectedErr := errors.New("task-err")
	fakeContainer.NewTaskReturns(nil, expectedErr)

	s.client.NewContainerReturns(fakeContainer, nil)

	_, err := s.backend.Create(minimumValidGdnSpec)
	s.EqualError(errors.Unwrap(err), expectedErr.Error())

	s.Equal(1, fakeContainer.NewTaskCallCount())
}

func (s *BackendSuite) TestCreateContainerTaskStartFailure() {
	fakeTask := new(libcontainerdfakes.FakeTask)
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	s.client.NewContainerReturns(fakeContainer, nil)
	fakeContainer.NewTaskReturns(fakeTask, nil)
	fakeTask.StartReturns(errors.New("start-err"))

	_, err := s.backend.Create(minimumValidGdnSpec)
	s.Error(err)

	s.EqualError(errors.Unwrap(err), "start-err")
}

func (s *BackendSuite) TestCreateContainerSetsHandle() {
	fakeTask := new(libcontainerdfakes.FakeTask)
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	fakeContainer.IDReturns("handle")
	fakeContainer.NewTaskReturns(fakeTask, nil)

	s.client.NewContainerReturns(fakeContainer, nil)
	cont, err := s.backend.Create(minimumValidGdnSpec)
	s.NoError(err)

	s.Equal("handle", cont.Handle())

}

func (s *BackendSuite) TestContainersWithContainerdFailure() {
	s.client.ContainersReturns(nil, errors.New("err"))

	_, err := s.backend.Containers(nil)
	s.Error(err)
	s.Equal(1, s.client.ContainersCallCount())
}

func (s *BackendSuite) TestContainersWithInvalidPropertyFilters() {
	for _, tc := range []struct {
		desc   string
		filter map[string]string
	}{
		{
			desc: "empty key",
			filter: map[string]string{
				"": "bar",
			},
		},
		{
			desc: "empty value",
			filter: map[string]string{
				"foo": "",
			},
		},
	} {
		s.T().Run(tc.desc, func(t *testing.T) {
			_, err := s.backend.Containers(tc.filter)

			s.Error(err)
			s.Equal(0, s.client.ContainersCallCount())
		})
	}
}

func (s *BackendSuite) TestContainersWithProperProperties() {
	_, _ = s.backend.Containers(map[string]string{"foo": "bar", "caz": "zaz"})
	s.Equal(1, s.client.ContainersCallCount())

	_, labelSet := s.client.ContainersArgsForCall(0)
	s.ElementsMatch([]string{"labels.foo==bar", "labels.caz==zaz"}, labelSet)
}

func (s *BackendSuite) TestContainersConversion() {
	fakeContainer1 := new(libcontainerdfakes.FakeContainer)
	fakeContainer2 := new(libcontainerdfakes.FakeContainer)

	s.client.ContainersReturns([]containerd.Container{
		fakeContainer1, fakeContainer2,
	}, nil)

	containers, err := s.backend.Containers(nil)
	s.NoError(err)
	s.Equal(1, s.client.ContainersCallCount())
	s.Len(containers, 2)
}

func (s *BackendSuite) TestLookupEmptyHandleError() {
	_, err := s.backend.Lookup("")
	s.Equal("empty handle", err.Error())
}

func (s *BackendSuite) TestLookupCallGetContainerWithHandle() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeContainer.IDReturns("handle")
	s.client.GetContainerReturns(fakeContainer, nil)

	_, _ = s.backend.Lookup("handle")
	s.Equal(1, s.client.GetContainerCallCount())

	_, handle := s.client.GetContainerArgsForCall(0)
	s.Equal("handle", handle)
}

func (s *BackendSuite) TestLookupGetContainerError() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeContainer.IDReturns("handle")
	s.client.GetContainerReturns(fakeContainer, nil)

	s.client.GetContainerReturns(nil, errors.New("containerd-err"))

	_, err := s.backend.Lookup("handle")
	s.Error(err)
	s.EqualError(errors.Unwrap(err), "containerd-err")
}

func (s *BackendSuite) TestLookupGetContainerFails() {
	s.client.GetContainerReturns(nil, errors.New("err"))
	_, err := s.backend.Lookup("non-existent-handle")
	s.Error(err)
	s.EqualError(errors.Unwrap(err), "err")
}

func (s *BackendSuite) TestLookupGetNoContainerReturned() {
	s.client.GetContainerReturns(nil, errors.New("not found"))
	container, err := s.backend.Lookup("non-existent-handle")
	s.Error(err)
	s.Nil(container)
}

func (s *BackendSuite) TestLookupGetContainer() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeContainer.IDReturns("handle")
	s.client.GetContainerReturns(fakeContainer, nil)
	container, err := s.backend.Lookup("handle")
	s.NoError(err)
	s.NotNil(container)
	s.Equal("handle", container.Handle())
}

func (s *BackendSuite) TestDestroyEmptyHandleError() {
	err := s.backend.Destroy("")
	s.EqualError(err, "empty handle")
}

func (s *BackendSuite) TestDestroyGetContainerError() {
	s.client.GetContainerReturns(nil, errors.New("get-container-failed"))

	err := s.backend.Destroy("some-handle")
	s.EqualError(errors.Unwrap(err), "get-container-failed")
}

func (s *BackendSuite) TestDestroyGetTaskError() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	s.client.GetContainerReturns(fakeContainer, nil)

	expectedError := errors.New("get-task-failed")
	fakeContainer.TaskReturns(nil, expectedError)

	err := s.backend.Destroy("some handle")
	s.True(errors.Is(err, expectedError))
}

func (s *BackendSuite) TestDestroyGetTaskErrorNotFoundAndDeleteFails() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(nil, errdefs.ErrNotFound)

	expectedError := errors.New("delete-container-failed")
	fakeContainer.DeleteReturns(expectedError)

	err := s.backend.Destroy("some handle")
	s.True(errors.Is(err, expectedError))
}

func (s *BackendSuite) TestDestroyGetTaskErrorNotFoundAndDeleteSucceeds() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)

	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(nil, errdefs.ErrNotFound)

	err := s.backend.Destroy("some handle")

	s.Equal(1, fakeContainer.DeleteCallCount())
	s.NoError(err)
}

func (s *BackendSuite) TestDestroyKillTaskFails() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeTask := new(libcontainerdfakes.FakeTask)

	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(fakeTask, nil)

	expectedError := errors.New("kill-task-failed")
	s.killer.KillReturns(expectedError)

	err := s.backend.Destroy("some handle")
	s.True(errors.Is(err, expectedError))
	_, _, behaviour := s.killer.KillArgsForCall(0)
	s.Equal(backend.KillGracefully, behaviour)
}

func (s *BackendSuite) TestDestroyRemoveNetworkFails() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeTask := new(libcontainerdfakes.FakeTask)

	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(fakeTask, nil)

	expectedError := errors.New("remove-network-failed")
	s.network.RemoveReturns(expectedError)

	err := s.backend.Destroy("some handle")
	s.True(errors.Is(err, expectedError))
}

func (s *BackendSuite) TestDestroyDeleteTaskFails() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeTask := new(libcontainerdfakes.FakeTask)

	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(fakeTask, nil)

	expectedError := errors.New("delete-container-failed")
	fakeTask.DeleteReturns(nil, expectedError)

	err := s.backend.Destroy("some handle")
	s.True(errors.Is(err, expectedError))
}

func (s *BackendSuite) TestDestroyContainerDeleteFailsAndDeleteTaskSucceeds() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeTask := new(libcontainerdfakes.FakeTask)

	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(fakeTask, nil)

	expectedError := errors.New("delete-container-failed")
	fakeContainer.DeleteReturns(expectedError)

	err := s.backend.Destroy("some handle")
	s.True(errors.Is(err, expectedError))
}

func (s *BackendSuite) TestDestroySucceeds() {
	fakeContainer := new(libcontainerdfakes.FakeContainer)
	fakeTask := new(libcontainerdfakes.FakeTask)
	s.client.GetContainerReturns(fakeContainer, nil)
	fakeContainer.TaskReturns(fakeTask, nil)

	err := s.backend.Destroy("some handle")
	s.NoError(err)
}

func (s *BackendSuite) TestStart() {
	err := s.backend.Start()
	s.NoError(err)
	s.Equal(1, s.client.InitCallCount())
}

func (s *BackendSuite) TestStartInitError() {
	s.client.InitReturns(errors.New("init failed"))
	err := s.backend.Start()
	s.EqualError(errors.Unwrap(err), "init failed")
}

func (s *BackendSuite) TestStop() {
	s.backend.Stop()
	s.Equal(1, s.client.StopCallCount())
}
<|endoftext|>


<commit_before>package workflow

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	aw "github.com/deanishe/awgo"
	"github.com/rkoval/alfred-aws-console-services-workflow/aliases"
	"github.com/rkoval/alfred-aws-console-services-workflow/awsconfig"
	"github.com/rkoval/alfred-aws-console-services-workflow/awsworkflow"
	"github.com/rkoval/alfred-aws-console-services-workflow/parsers"
	"github.com/rkoval/alfred-aws-console-services-workflow/searchers"
	"github.com/rkoval/alfred-aws-console-services-workflow/searchers/searchutil"
	"github.com/rkoval/alfred-aws-console-services-workflow/util"
)

func Run(wf *aw.Workflow, rawQuery string, transport http.RoundTripper, forceFetch, openAll bool, ymlPath string) {
	log.Println("using workflow cacheDir: " + wf.CacheDir())
	log.Println("using workflow dataDir: " + wf.DataDir())

	parser := parsers.NewParser(rawQuery)
	query, awsServices := parser.Parse(ymlPath)
	defer finalize(wf, query)

	log.Printf("using query: %#v", query)

	if query.RegionQuery != nil {
		for _, region := range awsconfig.AllAWSRegions {
			autocomplete := strings.Replace(rawQuery, aliases.OverrideAwsRegion+*query.RegionQuery, aliases.OverrideAwsRegion+region.Name+" ", 1)
			wf.NewItem(region.Name).
				Subtitle(region.Description).
				Icon(aw.IconWeb).
				Autocomplete(autocomplete).
				UID(region.Name)
		}
		log.Printf("filtering with region override %q", *query.RegionQuery)
		res := wf.Filter(*query.RegionQuery)
		log.Printf("%d results match %q", len(res), *query.RegionQuery)
		return
	}

	if query.ProfileQuery != nil {
		for _, profile := range awsconfig.GetAwsProfiles() {
			autocomplete := strings.Replace(rawQuery, aliases.OverrideAwsProfile+*query.ProfileQuery, aliases.OverrideAwsProfile+profile.Name+" ", 1)
			item := wf.NewItem(profile.Name).
				Icon(aw.IconAccount).
				Autocomplete(autocomplete).
				UID(profile.Name)

			if profile.Region != "" {
				item.Subtitle(fmt.Sprintf("🌎 %s", profile.Region))
			} else {
				item.Subtitle("⚠ This profile does not specify a region. Functionality will be limited")
			}
		}
		log.Printf("filtering with profile override %q", *query.ProfileQuery)
		res := wf.Filter(*query.ProfileQuery)
		log.Printf("%d results match %q", len(res), *query.ProfileQuery)
		return
	}

	cfg := awsworkflow.InitAWS(transport, query.ProfileOverride, query.GetRegionOverride())
	searchArgs := searchutil.SearchArgs{
		Cfg:        cfg,
		ForceFetch: forceFetch,
		FullQuery:  rawQuery,
		Profile:    util.GetProfile(cfg),
	}

	if query.IsEmpty() {
		handleEmptyQuery(wf, searchArgs)
		return
	}

	if query.HasOpenAll {
		handleOpenAll(wf, query.Service, awsServices, openAll, rawQuery, cfg)
		return
	}

	if query.Service == nil || (!query.HasTrailingWhitespace && query.SubService == nil && !query.HasDefaultSearchAlias && query.RemainingQuery == "") {
		if query.Service == nil {
			searchArgs.Query = query.RemainingQuery
		} else if query.Service.ShortName != "" {
			searchArgs.Query = strings.ToLower(query.Service.ShortName)
		} else {
			searchArgs.Query = query.Service.Id
		}
		log.Printf("using searcher associated with services with query %q", searchArgs.Query)
		SearchServices(wf, awsServices, searchArgs)
	} else {
		if !query.HasDefaultSearchAlias && (query.Service.SubServices == nil || len(query.Service.SubServices) <= 0) {
			handleUnimplemented(wf, query.Service, nil, fmt.Sprintf("%s doesn't have sub-services configured (yet)", query.Service.Id), searchArgs)
			return
		}

		searchArgs.GetRegionFunc = query.Service.GetRegion

		if query.HasDefaultSearchAlias || query.SubService != nil && (query.HasTrailingWhitespace || query.RemainingQuery != "") {
			serviceId := query.Service.Id
			if query.SubService != nil {
				serviceId += "_" + query.SubService.Id
			}
			log.Println("using searcher associated with " + serviceId)
			searcher := searchers.SearchersByServiceId[serviceId]
			if searcher != nil {
				searchArgs.Query = query.RemainingQuery
				err := searcher.Search(wf, searchArgs)
				if err != nil {
					wf.FatalError(err)
				}
			} else {
				handleUnimplemented(wf, query.Service, query.SubService, fmt.Sprintf("No searcher for `%s %s` (yet)", query.Service.Id, query.SubService.Id), searchArgs)
				return
			}
		} else {
			log.Println("using searcher associated with sub-services")
			if query.SubService != nil {
				searchArgs.Query = query.SubService.Id
			} else {
				searchArgs.Query = query.RemainingQuery
			}
			SearchSubServices(wf, *query.Service, searchArgs)
		}
	}

	if searchArgs.Query != "" {
		log.Printf("filtering with query %q", searchArgs.Query)
		res := wf.Filter(searchArgs.Query)
		log.Printf("%d results match %q", len(res), searchArgs.Query)
	}
}

func finalize(wf *aw.Workflow, query *parsers.Query) {
	if wf.IsEmpty() {
		title := ""
		subtitle := ""
		if query.RegionQuery != nil {
			title = "No matching regions found"
			subtitle = fmt.Sprintf("Try starting over with \"%s\" again to see the full list", aliases.OverrideAwsRegion)
		} else if query.ProfileQuery != nil {
			title = "No matching profiles found"
			subtitle = fmt.Sprintf("Try starting over with \"%s\" again to see the full list", aliases.OverrideAwsProfile)
		} else {
			title = "No matching services found"
			subtitle = "Try another query (example: `aws ec2 instances`)"
		}
		wf.NewItem(title).
			Subtitle(subtitle).
			Icon(aw.IconNote)
		handleUpdateAvailable(wf)
	}
	wf.SendFeedback()
}

func handleEmptyQuery(wf *aw.Workflow, searchArgs searchutil.SearchArgs) {
	log.Println("no search type parsed")
	wf.NewItem("Search for an AWS Service ...").
		Subtitle("e.g., cloudformation, ec2, s3 ...")

	if searchArgs.Profile == "" {
		util.NewURLItem(wf, "No profile configured").
			Subtitle("Select this option to open AWS docs on how to configure").
			Arg("https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#creating-the-config-file").
			Icon(aw.IconWarning)
	} else {
		wf.NewItem("Using profile \"" + searchArgs.Profile + "\"").
			Subtitle("Use \"" + aliases.OverrideAwsProfile + "\" to override for the current query").
			Icon(aw.IconAccount)
	}

	if searchArgs.Cfg.Region == "" {
		util.NewURLItem(wf, "No region configured for this profile").
			Subtitle("Select this option to open AWS docs on how to configure").
			Arg("https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#creating-the-config-file").
			Icon(aw.IconWarning)
	} else {
		wf.NewItem("Using region \"" + searchArgs.Cfg.Region + "\"").
			Subtitle("Use \"" + aliases.OverrideAwsRegion + "\" to override for the current query").
			Icon(aw.IconWeb)
	}

	util.NewURLItem(wf, "Like this workflow? Consider donating! 😻").
		Subtitle("Select this option to open this project's Patreon").
		Arg("https://www.patreon.com/rkoval_alfred_aws_console_services_workflow").
		Icon(aw.IconFavorite)

	if wf.UpdateCheckDue() {
		if err := wf.CheckForUpdate(); err != nil {
			wf.FatalError(err)
		}
	}
	handleUpdateAvailable(wf)
}

func handleUnimplemented(wf *aw.Workflow, awsService, subService *awsworkflow.AwsService, header string, searchArgs searchutil.SearchArgs) {
	searchArgs.IgnoreAutocompleteTerm = true
	if subService == nil {
		AddServiceToWorkflow(wf, *awsService, searchArgs)
	} else {
		AddSubServiceToWorkflow(wf, *awsService, *subService, searchArgs)
	}
	util.NewURLItem(wf, header).
		Subtitle("Select this result to open the contributing guide to easily add them!").
		Arg("https://github.com/rkoval/alfred-aws-console-services-workflow/blob/master/CONTRIBUTING.md").
		Icon(aw.IconNote)
	handleUpdateAvailable(wf)
}

func handleUpdateAvailable(wf *aw.Workflow) {
	if wf.UpdateAvailable() {
		util.NewURLItem(wf, fmt.Sprintf("Update available (current version: %s)", wf.Version())).
			Subtitle("Select this result to navigate to download").
			Arg("https://github.com/rkoval/alfred-aws-console-services-workflow/releases").
			Icon(aw.IconInfo)
	}
}

func handleOpenAll(wf *aw.Workflow, awsService *awsworkflow.AwsService, allAwsServices []awsworkflow.AwsService, openAll bool, rawQuery string, cfg aws.Config) {
	if openAll {
		if awsService == nil {
			for _, awsService := range allAwsServices {
				openServiceInBrowser(wf, awsService, awsService, cfg)
			}
		} else {
			for _, subService := range awsService.SubServices {
				openServiceInBrowser(wf, subService, *awsService, cfg)
			}
		}
	} else {
		var length int
		var entityName string
		if awsService == nil {
			length = len(allAwsServices)
			entityName = "services"
		} else {
			length = len(awsService.SubServices)
			entityName = awsService.Id + " sub-services"
		}

		title := fmt.Sprintf("Open the %d %s in browser", length, entityName)
		cmd := fmt.Sprintf(`%s -query="%s" -open_all`, os.Args[0], rawQuery)
		wf.NewItem(title).
			Subtitle("Fair warning: this may briefly overload your system").
			Icon(aw.IconWarning).
			Arg(cmd).
			Var("action", "run-script").
			Valid(true)
	}
}

func openServiceInBrowser(wf *aw.Workflow, awsService, awsServiceForRegion awsworkflow.AwsService, cfg aws.Config) {
	cmd := exec.Command("open", util.ConstructAWSConsoleUrl(awsService.Url, awsServiceForRegion.GetRegion(cfg)))
	if err := wf.RunInBackground("open-service-in-browser-"+awsService.Id, cmd); err != nil {
		panic(err)
	}
	time.Sleep(250 * time.Millisecond) // sleep so that tabs are more-or-less opened in the order by which this function is called
}
<commit_msg>added writing to json file all services opened via OPEN_ALL for automated validation<commit_after>package workflow

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	aw "github.com/deanishe/awgo"
	"github.com/rkoval/alfred-aws-console-services-workflow/aliases"
	"github.com/rkoval/alfred-aws-console-services-workflow/awsconfig"
	"github.com/rkoval/alfred-aws-console-services-workflow/awsworkflow"
	"github.com/rkoval/alfred-aws-console-services-workflow/parsers"
	"github.com/rkoval/alfred-aws-console-services-workflow/searchers"
	"github.com/rkoval/alfred-aws-console-services-workflow/searchers/searchutil"
	"github.com/rkoval/alfred-aws-console-services-workflow/util"
)

func Run(wf *aw.Workflow, rawQuery string, transport http.RoundTripper, forceFetch, openAll bool, ymlPath string) {
	log.Println("using workflow cacheDir: " + wf.CacheDir())
	log.Println("using workflow dataDir: " + wf.DataDir())

	parser := parsers.NewParser(rawQuery)
	query, awsServices := parser.Parse(ymlPath)
	defer finalize(wf, query)

	log.Printf("using query: %#v", query)

	if query.RegionQuery != nil {
		for _, region := range awsconfig.AllAWSRegions {
			autocomplete := strings.Replace(rawQuery, aliases.OverrideAwsRegion+*query.RegionQuery, aliases.OverrideAwsRegion+region.Name+" ", 1)
			wf.NewItem(region.Name).
				Subtitle(region.Description).
				Icon(aw.IconWeb).
				Autocomplete(autocomplete).
				UID(region.Name)
		}
		log.Printf("filtering with region override %q", *query.RegionQuery)
		res := wf.Filter(*query.RegionQuery)
		log.Printf("%d results match %q", len(res), *query.RegionQuery)
		return
	}

	if query.ProfileQuery != nil {
		for _, profile := range awsconfig.GetAwsProfiles() {
			autocomplete := strings.Replace(rawQuery, aliases.OverrideAwsProfile+*query.ProfileQuery, aliases.OverrideAwsProfile+profile.Name+" ", 1)
			item := wf.NewItem(profile.Name).
				Icon(aw.IconAccount).
				Autocomplete(autocomplete).
				UID(profile.Name)

			if profile.Region != "" {
				item.Subtitle(fmt.Sprintf("🌎 %s", profile.Region))
			} else {
				item.Subtitle("⚠ This profile does not specify a region. Functionality will be limited")
			}
		}
		log.Printf("filtering with profile override %q", *query.ProfileQuery)
		res := wf.Filter(*query.ProfileQuery)
		log.Printf("%d results match %q", len(res), *query.ProfileQuery)
		return
	}

	cfg := awsworkflow.InitAWS(transport, query.ProfileOverride, query.GetRegionOverride())
	searchArgs := searchutil.SearchArgs{
		Cfg:        cfg,
		ForceFetch: forceFetch,
		FullQuery:  rawQuery,
		Profile:    util.GetProfile(cfg),
	}

	if query.IsEmpty() {
		handleEmptyQuery(wf, searchArgs)
		return
	}

	if query.HasOpenAll {
		handleOpenAll(wf, query.Service, awsServices, openAll, rawQuery, cfg)
		return
	}

	if query.Service == nil || (!query.HasTrailingWhitespace && query.SubService == nil && !query.HasDefaultSearchAlias && query.RemainingQuery == "") {
		if query.Service == nil {
			searchArgs.Query = query.RemainingQuery
		} else if query.Service.ShortName != "" {
			searchArgs.Query = strings.ToLower(query.Service.ShortName)
		} else {
			searchArgs.Query = query.Service.Id
		}
		log.Printf("using searcher associated with services with query %q", searchArgs.Query)
		SearchServices(wf, awsServices, searchArgs)
	} else {
		if !query.HasDefaultSearchAlias && (query.Service.SubServices == nil || len(query.Service.SubServices) <= 0) {
			handleUnimplemented(wf, query.Service, nil, fmt.Sprintf("%s doesn't have sub-services configured (yet)", query.Service.Id), searchArgs)
			return
		}

		searchArgs.GetRegionFunc = query.Service.GetRegion

		if query.HasDefaultSearchAlias || query.SubService != nil && (query.HasTrailingWhitespace || query.RemainingQuery != "") {
			serviceId := query.Service.Id
			if query.SubService != nil {
				serviceId += "_" + query.SubService.Id
			}
			log.Println("using searcher associated with " + serviceId)
			searcher := searchers.SearchersByServiceId[serviceId]
			if searcher != nil {
				searchArgs.Query = query.RemainingQuery
				err := searcher.Search(wf, searchArgs)
				if err != nil {
					wf.FatalError(err)
				}
			} else {
				handleUnimplemented(wf, query.Service, query.SubService, fmt.Sprintf("No searcher for `%s %s` (yet)", query.Service.Id, query.SubService.Id), searchArgs)
				return
			}
		} else {
			log.Println("using searcher associated with sub-services")
			if query.SubService != nil {
				searchArgs.Query = query.SubService.Id
			} else {
				searchArgs.Query = query.RemainingQuery
			}
			SearchSubServices(wf, *query.Service, searchArgs)
		}
	}

	if searchArgs.Query != "" {
		log.Printf("filtering with query %q", searchArgs.Query)
		res := wf.Filter(searchArgs.Query)
		log.Printf("%d results match %q", len(res), searchArgs.Query)
	}
}

func finalize(wf *aw.Workflow, query *parsers.Query) {
	if wf.IsEmpty() {
		title := ""
		subtitle := ""
		if query.RegionQuery != nil {
			title = "No matching regions found"
			subtitle = fmt.Sprintf("Try starting over with \"%s\" again to see the full list", aliases.OverrideAwsRegion)
		} else if query.ProfileQuery != nil {
			title = "No matching profiles found"
			subtitle = fmt.Sprintf("Try starting over with \"%s\" again to see the full list", aliases.OverrideAwsProfile)
		} else {
			title = "No matching services found"
			subtitle = "Try another query (example: `aws ec2 instances`)"
		}
		wf.NewItem(title).
			Subtitle(subtitle).
			Icon(aw.IconNote)
		handleUpdateAvailable(wf)
	}
	wf.SendFeedback()
}

func handleEmptyQuery(wf *aw.Workflow, searchArgs searchutil.SearchArgs) {
	log.Println("no search type parsed")
	wf.NewItem("Search for an AWS Service ...").
		Subtitle("e.g., cloudformation, ec2, s3 ...")

	if searchArgs.Profile == "" {
		util.NewURLItem(wf, "No profile configured").
			Subtitle("Select this option to open AWS docs on how to configure").
			Arg("https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#creating-the-config-file").
			Icon(aw.IconWarning)
	} else {
		wf.NewItem("Using profile \"" + searchArgs.Profile + "\"").
			Subtitle("Use \"" + aliases.OverrideAwsProfile + "\" to override for the current query").
			Icon(aw.IconAccount)
	}

	if searchArgs.Cfg.Region == "" {
		util.NewURLItem(wf, "No region configured for this profile").
			Subtitle("Select this option to open AWS docs on how to configure").
			Arg("https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#creating-the-config-file").
			Icon(aw.IconWarning)
	} else {
		wf.NewItem("Using region \"" + searchArgs.Cfg.Region + "\"").
			Subtitle("Use \"" + aliases.OverrideAwsRegion + "\" to override for the current query").
			Icon(aw.IconWeb)
	}

	util.NewURLItem(wf, "Like this workflow? Consider donating! 😻").
		Subtitle("Select this option to open this project's Patreon").
		Arg("https://www.patreon.com/rkoval_alfred_aws_console_services_workflow").
		Icon(aw.IconFavorite)

	if wf.UpdateCheckDue() {
		if err := wf.CheckForUpdate(); err != nil {
			wf.FatalError(err)
		}
	}
	handleUpdateAvailable(wf)
}

func handleUnimplemented(wf *aw.Workflow, awsService, subService *awsworkflow.AwsService, header string, searchArgs searchutil.SearchArgs) {
	searchArgs.IgnoreAutocompleteTerm = true
	if subService == nil {
		AddServiceToWorkflow(wf, *awsService, searchArgs)
	} else {
		AddSubServiceToWorkflow(wf, *awsService, *subService, searchArgs)
	}
	util.NewURLItem(wf, header).
		Subtitle("Select this result to open the contributing guide to easily add them!").
		Arg("https://github.com/rkoval/alfred-aws-console-services-workflow/blob/master/CONTRIBUTING.md").
		Icon(aw.IconNote)
	handleUpdateAvailable(wf)
}

func handleUpdateAvailable(wf *aw.Workflow) {
	if wf.UpdateAvailable() {
		util.NewURLItem(wf, fmt.Sprintf("Update available (current version: %s)", wf.Version())).
			Subtitle("Select this result to navigate to download").
			Arg("https://github.com/rkoval/alfred-aws-console-services-workflow/releases").
			Icon(aw.IconInfo)
	}
}

func handleOpenAll(wf *aw.Workflow, awsService *awsworkflow.AwsService, allAwsServices []awsworkflow.AwsService, openAll bool, rawQuery string, cfg aws.Config) {
	if openAll {
		var urls []string
		if awsService == nil {
			for _, awsService := range allAwsServices {
				urls = append(urls, openServiceInBrowser(wf, awsService, awsService, cfg))
			}
		} else {
			for _, subService := range awsService.SubServices {
				urls = append(urls, openServiceInBrowser(wf, subService, *awsService, cfg))
			}
		}
		// write these to disk so that we can use this file to run automated validation against the pages as they redirect (or error) in AWS
		lastOpenedUrlsPath := wf.CacheDir() + "/last-opened-urls.json"
		urlBytes, err := json.Marshal(urls)
		if err != nil {
			panic(err)
		}
		err = ioutil.WriteFile(lastOpenedUrlsPath, urlBytes, 0600)
		if err != nil {
			panic(err)
		}

	} else {
		var length int
		var entityName string
		if awsService == nil {
			length = len(allAwsServices)
			entityName = "services"
		} else {
			length = len(awsService.SubServices)
			entityName = awsService.Id + " sub-services"
		}

		title := fmt.Sprintf("Open the %d %s in browser", length, entityName)
		cmd := fmt.Sprintf(`%s -query="%s" -open_all`, os.Args[0], rawQuery)
		wf.NewItem(title).
			Subtitle("Fair warning: this may briefly overload your system").
			Icon(aw.IconWarning).
			Arg(cmd).
			Var("action", "run-script").
			Valid(true)
	}
}

func openServiceInBrowser(wf *aw.Workflow, awsService, awsServiceForRegion awsworkflow.AwsService, cfg aws.Config) string {
	url := util.ConstructAWSConsoleUrl(awsService.Url, awsServiceForRegion.GetRegion(cfg))
	log.Printf("opening url: %s ...", url)
	cmd := exec.Command("open", url)
	if err := wf.RunInBackground("open-service-in-browser-"+awsService.Id, cmd); err != nil {
		panic(err)
	}
	time.Sleep(250 * time.Millisecond) // sleep so that tabs are more-or-less opened in the order by which this function is called
	return url
}
<|endoftext|>


<commit_before>package mavlink

import (
	"io"
	"log"
	"time"
)

type Connection struct {
	wrappedConn        io.ReadWriter
	channel            chan *Packet
	closeTime          chan time.Time
	closeError         chan error
	closed             bool
	localComponentSeq  [256]uint8
	remoteComponentSeq [256]uint8
	systemID           uint8
}

func NewConnection(wrappedConn io.ReadWriter, systemID uint8) *Connection {
	conn := &Connection{
		wrappedConn: wrappedConn,
		channel:     make(chan *Packet, 64),
		closeTime:   make(chan time.Time, 1),
		closeError:  make(chan error),
		systemID:    systemID,
	}
	go conn.sendLoop()
	go conn.receiveLoop()
	return conn
}

func (conn *Connection) receiveLoop() {
	for !conn.closed {

		packet, err := Receive(conn.wrappedConn)
		if err != nil {
			if LogAllErrors {
				log.Println(err)
			}
			if err == io.EOF {
				conn.close() // make sure everything is in closed state
				return
			} else {
				conn.channel <- &Packet{Err: err}
				continue
			}
		}

		component := packet.Header.ComponentID
		loss := int(packet.Header.PacketSequence) - int(conn.remoteComponentSeq[component]) - 1
		if loss < 0 {
			loss = 255 - loss
		}
		if loss > 0 {
			err := ErrPacketLoss(loss)
			if LogAllErrors {
				log.Println(err)
			}
			conn.channel <- &Packet{Err: err}
		}

		conn.channel <- packet
	}
}

func (conn *Connection) sendLoop() {
	for !conn.closed {
		select {
		case packet := <-conn.channel:
			conn.localComponentSeq[packet.Header.ComponentID]++
			packet.Header.PacketSequence = conn.localComponentSeq[packet.Header.ComponentID]

			_, packet.Err = packet.WriteTo(conn.wrappedConn)
			if packet.Err != nil {
				if LogAllErrors {
					log.Println(packet.Err)
				}
				if packet.OnErr != nil {
					packet.OnErr(packet)
				}
			}
		default:
			time.Sleep(time.Millisecond)
		}
	}
}

func (conn *Connection) Send(componentID uint8, message Message, onErr func(*Packet)) {
	if conn.closed {
		return
	}

	packet := NewPacket(conn.systemID, componentID, 0, message)
	packet.OnErr = onErr

	conn.channel <- packet
}

func (conn *Connection) Receive() *Packet {
	if conn.closed {
		return nil
	}

	return <-conn.channel
}

func (conn *Connection) close() (err error) {
	defer func() { conn.closed = true }()

	if closer, ok := conn.wrappedConn.(io.Closer); ok {
		err = closer.Close()
		if err != nil && LogAllErrors {
			log.Println(err)
		}
	}

	return err
}

func (conn *Connection) Close(timeout time.Time) error {
	return conn.close()
	// conn.closeTime <- timeout
	// return <-conn.closeError
}
<commit_msg>Close with timeout for write buffer<commit_after>package mavlink

import (
	"io"
	"log"
	"time"
)

type Connection struct {
	wrappedConn        io.ReadWriter
	channel            chan *Packet
	closeTimeout       time.Time
	sendLoopFinished   chan struct{}
	closed             bool
	localComponentSeq  [256]uint8
	remoteComponentSeq [256]uint8
	systemID           uint8
}

func NewConnection(wrappedConn io.ReadWriter, systemID uint8) *Connection {
	conn := &Connection{
		wrappedConn:      wrappedConn,
		channel:          make(chan *Packet, 64),
		sendLoopFinished: make(chan struct{}),
		systemID:         systemID,
	}
	go conn.sendLoop()
	go conn.receiveLoop()
	return conn
}

func (conn *Connection) receiveLoop() {
	for !conn.closed {

		packet, err := Receive(conn.wrappedConn)
		if err != nil {
			if LogAllErrors {
				log.Println(err)
			}
			if err == io.EOF {
				conn.close() // make sure everything is in closed state
				return
			} else {
				conn.channel <- &Packet{Err: err}
				continue
			}
		}

		component := packet.Header.ComponentID
		loss := int(packet.Header.PacketSequence) - int(conn.remoteComponentSeq[component]) - 1
		if loss < 0 {
			loss = 255 - loss
		}
		if loss > 0 {
			err := ErrPacketLoss(loss)
			if LogAllErrors {
				log.Println(err)
			}
			conn.channel <- &Packet{Err: err}
		}

		conn.channel <- packet
	}
}

func (conn *Connection) sendLoop() {
	for !conn.closed || time.Now().Before(conn.closeTimeout) {
		select {
		case packet := <-conn.channel:
			conn.localComponentSeq[packet.Header.ComponentID]++
			packet.Header.PacketSequence = conn.localComponentSeq[packet.Header.ComponentID]

			_, packet.Err = packet.WriteTo(conn.wrappedConn)
			if packet.Err != nil {
				if LogAllErrors {
					log.Println(packet.Err)
				}
				if packet.OnErr != nil {
					go packet.OnErr(packet)
				}
			}
		default:
			if conn.closed {
				break
			}
			time.Sleep(time.Millisecond)
		}
	}
	conn.sendLoopFinished <- struct{}{}
}

func (conn *Connection) Send(componentID uint8, message Message, onErr func(*Packet)) {
	if conn.closed {
		return
	}

	packet := NewPacket(conn.systemID, componentID, 0, message)
	packet.OnErr = onErr

	conn.channel <- packet
}

func (conn *Connection) Receive() *Packet {
	if conn.closed {
		return nil
	}

	return <-conn.channel
}

func (conn *Connection) close() (err error) {
	defer func() { conn.closed = true }()

	if closer, ok := conn.wrappedConn.(io.Closer); ok {
		err = closer.Close()
		if err != nil && LogAllErrors {
			log.Println(err)
		}
	}

	return err
}

// Close stops reading from wrappedConn after the current read finishes
// and stops writing when there are no more buffered packets or
// the current time has reached timeout.
// After that, if wrappedConn implements io.Closer, its Close method will
// be called and the result returned.
func (conn *Connection) Close(timeout time.Time) error {
	conn.closeTimeout = timeout
	conn.closed = true
	<-conn.sendLoopFinished
	return conn.close()
}
<|endoftext|>


<commit_before>package mailserver

import (
	"errors"
	"time"
)

const (
	maxMessagesRequestPayloadLimit = 1000
)

// MessagesRequestPayload is a payload sent to the Mail Server.
type MessagesRequestPayload struct {
	// Lower is a lower bound of time range for which messages are requested.
	Lower uint32
	// Upper is a lower bound of time range for which messages are requested.
	Upper uint32
	// Bloom is a bloom filter to filter envelopes.
	Bloom []byte
	// Limit is the max number of envelopes to return.
	Limit uint32
	// Cursor is used for pagination of the results.
	Cursor []byte
	// Batch set to true indicates that the client supports batched response.
	Batch bool
}

func (r *MessagesRequestPayload) SetDefaults() {
	if r.Limit == 0 {
		r.Limit = maxQueryLimit
	}

	if r.Upper == 0 {
		r.Upper = uint32(time.Now().Unix() + whisperTTLSafeThreshold)
	}
}

func (r MessagesRequestPayload) Validate() error {
	if r.Upper < r.Lower {
		return errors.New("query range is invalid: lower > upper")
	}
	if r.Upper-r.Lower > uint32(maxQueryRange.Seconds()) {
		return errors.New("query range must be smaller or equal to 24 hours")
	}
	if len(r.Bloom) == 0 {
		return errors.New("bloom filter is empty")
	}
	if r.Limit > maxMessagesRequestPayloadLimit {
		return errors.New("limit exceeds the maximum allowed value")
	}
	return nil
}
<commit_msg>Remove 24h time range validation from maislerver request (#1792)<commit_after>package mailserver

import (
	"errors"
	"time"
)

const (
	maxMessagesRequestPayloadLimit = 1000
)

// MessagesRequestPayload is a payload sent to the Mail Server.
type MessagesRequestPayload struct {
	// Lower is a lower bound of time range for which messages are requested.
	Lower uint32
	// Upper is a lower bound of time range for which messages are requested.
	Upper uint32
	// Bloom is a bloom filter to filter envelopes.
	Bloom []byte
	// Limit is the max number of envelopes to return.
	Limit uint32
	// Cursor is used for pagination of the results.
	Cursor []byte
	// Batch set to true indicates that the client supports batched response.
	Batch bool
}

func (r *MessagesRequestPayload) SetDefaults() {
	if r.Limit == 0 {
		r.Limit = maxQueryLimit
	}

	if r.Upper == 0 {
		r.Upper = uint32(time.Now().Unix() + whisperTTLSafeThreshold)
	}
}

func (r MessagesRequestPayload) Validate() error {
	if r.Upper < r.Lower {
		return errors.New("query range is invalid: lower > upper")
	}
	if len(r.Bloom) == 0 {
		return errors.New("bloom filter is empty")
	}
	if r.Limit > maxMessagesRequestPayloadLimit {
		return errors.New("limit exceeds the maximum allowed value")
	}
	return nil
}
<|endoftext|>


<commit_before>package server

type ContactStore struct {
}

func NewContactStore() *ContactStore {
	return &ContactStore{}
}

func (c *ContactStore) AddContact(userId string, contactId string) bool {
	return true
}

func (c *ContactStore) RemoveContact(userId string, contactId string) bool {
	return true
}

func (c *ContactStore) ListContacts(userId string) ([]string, bool) {
	return []string{}, true
}

func (c *ContactStore) SearchContact(userId string, contactName string) string {
	return ""
}
<commit_msg>Implement inmemory contact store<commit_after>package server

import "log"

type ContactStore struct {
	contacts map[string][]string
}

func NewContactStore() *ContactStore {
	return &ContactStore{
		contacts: make(map[string][]string),
	}
}

func (c *ContactStore) AddContact(userId string, contactId string) bool {
	userContacts, ok := c.contacts[userId]
	if !ok {
		c.contacts[userId] = []string{}
		userContacts = c.contacts[userId]
	}

	userContacts = append(userContacts, contactId)
	return true
}

func (c *ContactStore) RemoveContact(userId string, contactId string) bool {
	userContacts, ok := c.contacts[userId]
	if !ok {
		log.Println("No contacts for user", userId)
		return false
	}

	for i, uc := range userContacts {
		if uc == contactId {
			userContacts = append(userContacts[:i], userContacts[i+1:]...)
			return true
		}
	}

	return false
}

func (c *ContactStore) ListContacts(userId string) ([]string, bool) {
	userContacts, ok := c.contacts[userId]
	if !ok {
		return []string{}, false
	}

	return userContacts, true
}
<|endoftext|>


<commit_before>//Email is designed to be a package providing an "email interface for humans."
//Designed to be robust and flexible, the email package will make sending email easy without getting in the way.
package email

import (
	"net/mail"
)

//Email is the type used for email messages
type Email struct {
	From        string
	To          []string
	Bcc         []string
	Cc          []string
	Subject     []string
	Text        []byte //Plaintext message (optional)
	Html        []byte //Html message (optional)
	Headers     []mail.Header
	Attachments []Attachment //Might be a map soon - stay tuned.
}

//Send an email using the given host and SMTP auth (optional)
func (e Email) Send() {

}

type Attachment struct {
	Filename string
}
<commit_msg>Update email.go<commit_after>//Email is designed to be a package providing an "email interface for humans."
//Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
package email

import (
	"net/mail"
)

//Email is the type used for email messages
type Email struct {
	From        string
	To          []string
	Bcc         []string
	Cc          []string
	Subject     []string
	Text        []byte //Plaintext message (optional)
	Html        []byte //Html message (optional)
	Headers     []mail.Header
	Attachments []Attachment //Might be a map soon - stay tuned.
}

//Send an email using the given host and SMTP auth (optional)
func (e Email) Send() {

}

type Attachment struct {
	Filename string
}
<|endoftext|>


<commit_before>// ClientStarter
package main

import (
	"bytes"
	"fmt"
	"github.com/jzipfler/htw/ava/client"
	"github.com/jzipfler/htw/ava/server"
	"log"
)

var (
	loggingBuffer bytes.Buffer
	logger        *log.Logger
)

func main() {
	initializeLogger("LOG::: ")
	client := client.New()
	serverObject := server.New()
	logger.Printf("%T\n", client)
	logger.Printf("%T\n", serverObject)
	logger.Println("Client: " + client.String() + "\nServer: " + serverObject.String())
	error := client.SetIpAddressAsString("1.2.3.4")
	if error != nil {
		logger.Fatalln(error)
		return
	}
	client.SetClientName("First")
	logger.Println(client)

	printAndClearLoggerContent()
}

func initializeLogger(preface string) {
	logger = log.New(&loggingBuffer, preface, log.Lshortfile)
}

func printAndClearLoggerContent() {
	if loggingBuffer.Len() != 0 {
		fmt.Println(&loggingBuffer)
		loggingBuffer.Reset()
	}
}
<commit_msg>Implemented the first tries to the ClientStarter.<commit_after>// ClientStarter
package main

import (
	"bytes"
	"fmt"
	"github.com/jzipfler/htw/ava/client"
	"github.com/jzipfler/htw/ava/server"
	"log"
)

var (
	loggingBuffer bytes.Buffer
	logger        *log.Logger
)

func main() {
	//initializeLogger("LOG::: ")
	client := client.New()
	serverObject := server.New()
	printMessage("Client: " + client.String() + "\n\t\tServer: " + serverObject.String())
	error := client.SetIpAddressAsString("1.2.3.4")
	if error != nil {
		printMessage(error.Error())
		return
	}
	client.SetClientName("First")
	printMessage(client.String())

	doServerStuff(serverObject)

	printAndClearLoggerContent()
}

func doServerStuff(serverObject server.NetworkServer) {
	serverObject.SetClientName("Server1")
	serverObject.SetIpAddressAsString("127.0.0.1")
	serverObject.SetPort(15108)
	serverObject.SetUsedProtocol("tcp")
	printMessage(serverObject.String())
	err := server.StartServer(serverObject, logger)
	if err != nil {
		return
	}
	defer server.StopServer()
	//for {
	//	connection := server.ReceiveMessage()
	//	printMessage(connection.LocalAddr().String())
	//}
}

// Creates a new logger which uses a buffer where he collects the messages.
func initializeLogger(preface string) {
	logger = log.New(&loggingBuffer, preface, log.Lshortfile)
}

func printMessage(message string) {
	if logger == nil {
		log.Println(message)
	} else {
		logger.Println(message)
	}
}

func printAndClearLoggerContent() {
	if loggingBuffer.Len() != 0 {
		fmt.Println(&loggingBuffer)
		loggingBuffer.Reset()
	}
}
<|endoftext|>