Refactored file management
This commit is contained in:
parent
36b58227ea
commit
8790779bce
|
@ -4,9 +4,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## [2.0.0] - unreleased
|
## [2.0.0] - unreleased
|
||||||
- Mailer has been removed. It has been replaced by Dialer and Sender.
|
- Mailer has been removed. It has been replaced by Dialer and Sender.
|
||||||
- `CreateFile` has been removed and `OpenFile` has been renamed into `NewFile`.
|
- `File` type and the `CreateFile` and `OpenFile` functions have been removed.
|
||||||
- `File` fields changed. The `File.Header` field replaces `File.MimeType`
|
- `Message.Attach` and `Message.Embed` have a new signature.
|
||||||
and `File.ContentID`.
|
|
||||||
- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
|
- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
|
||||||
instead.
|
instead.
|
||||||
- `Message.Export` has been removed. `Message.WriteTo` can be used instead.
|
- `Message.Export` has been removed. `Message.WriteTo` can be used instead.
|
||||||
|
|
|
@ -15,7 +15,7 @@ func Example() {
|
||||||
m.SetAddressHeader("Cc", "dan@example.com", "Dan")
|
m.SetAddressHeader("Cc", "dan@example.com", "Dan")
|
||||||
m.SetHeader("Subject", "Hello!")
|
m.SetHeader("Subject", "Hello!")
|
||||||
m.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
|
m.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
|
||||||
m.Attach(gomail.NewFile("/home/Alex/lolcat.jpg"))
|
m.Attach("/home/Alex/lolcat.jpg")
|
||||||
|
|
||||||
d := gomail.NewPlainDialer("smtp.example.com", "user", "123456", 587)
|
d := gomail.NewPlainDialer("smtp.example.com", "user", "123456", 587)
|
||||||
|
|
||||||
|
|
126
message.go
126
message.go
|
@ -12,8 +12,8 @@ import (
|
||||||
type Message struct {
|
type Message struct {
|
||||||
header header
|
header header
|
||||||
parts []part
|
parts []part
|
||||||
attachments []*File
|
attachments []*file
|
||||||
embedded []*File
|
embedded []*file
|
||||||
charset string
|
charset string
|
||||||
encoding Encoding
|
encoding Encoding
|
||||||
hEncoder mimeEncoder
|
hEncoder mimeEncoder
|
||||||
|
@ -106,12 +106,20 @@ const (
|
||||||
|
|
||||||
// SetHeader sets a value to the given header field.
|
// SetHeader sets a value to the given header field.
|
||||||
func (m *Message) SetHeader(field string, value ...string) {
|
func (m *Message) SetHeader(field string, value ...string) {
|
||||||
for i := range value {
|
m.encodeHeader(value)
|
||||||
value[i] = m.encodeHeader(value[i])
|
|
||||||
}
|
|
||||||
m.header[field] = value
|
m.header[field] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Message) encodeHeader(values []string) {
|
||||||
|
for i := range values {
|
||||||
|
values[i] = m.encodeString(values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) encodeString(value string) string {
|
||||||
|
return m.hEncoder.Encode(m.charset, value)
|
||||||
|
}
|
||||||
|
|
||||||
// SetHeaders sets the message headers.
|
// SetHeaders sets the message headers.
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
|
@ -134,7 +142,7 @@ func (m *Message) SetAddressHeader(field, address, name string) {
|
||||||
|
|
||||||
// FormatAddress formats an address and a name as a valid RFC 5322 address.
|
// FormatAddress formats an address and a name as a valid RFC 5322 address.
|
||||||
func (m *Message) FormatAddress(address, name string) string {
|
func (m *Message) FormatAddress(address, name string) string {
|
||||||
enc := m.encodeHeader(name)
|
enc := m.encodeString(name)
|
||||||
if enc == name {
|
if enc == name {
|
||||||
m.buf.WriteByte('"')
|
m.buf.WriteByte('"')
|
||||||
for i := 0; i < len(name); i++ {
|
for i := 0; i < len(name); i++ {
|
||||||
|
@ -170,10 +178,6 @@ func hasSpecials(text string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) encodeHeader(value string) string {
|
|
||||||
return m.hEncoder.Encode(m.charset, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDateHeader sets a date to the given header field.
|
// SetDateHeader sets a date to the given header field.
|
||||||
func (m *Message) SetDateHeader(field string, date time.Time) {
|
func (m *Message) SetDateHeader(field string, date time.Time) {
|
||||||
m.header[field] = []string{m.FormatDate(date)}
|
m.header[field] = []string{m.FormatDate(date)}
|
||||||
|
@ -254,27 +258,61 @@ func (m *Message) getPartHeader(contentType string) header {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A File represents a file that can be attached or embedded in an email.
|
type file struct {
|
||||||
type File struct {
|
Name string
|
||||||
// Name represents the base name of the file. If the file is attached to the
|
Header map[string][]string
|
||||||
// message it is the name of the attachment.
|
CopyFunc func(w io.Writer) error
|
||||||
Name string
|
|
||||||
// Header represents the MIME header of the message part that contains the
|
|
||||||
// file content. It is empty by default. Mandatory headers are automatically
|
|
||||||
// added if they are not sent when sending the email.
|
|
||||||
Header map[string][]string
|
|
||||||
// Copier is a function run when the message is sent. It should copy the
|
|
||||||
// content of the file to w.
|
|
||||||
Copier func(w io.Writer) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFile creates a File from the given filename.
|
func (f *file) setHeader(field, value string) {
|
||||||
func NewFile(filename string) *File {
|
f.Header[field] = []string{value}
|
||||||
return &File{
|
}
|
||||||
Name: filepath.Base(filename),
|
|
||||||
|
// A FileSetting can be used as an argument in Message.Attach or Message.Embed.
|
||||||
|
type FileSetting func(*file)
|
||||||
|
|
||||||
|
// SetHeader is a file setting to set the MIME header of the message part that
|
||||||
|
// contains the file content.
|
||||||
|
//
|
||||||
|
// Mandatory headers are automatically added if they are not set when sending
|
||||||
|
// the email.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// h := map[string][]string{"Content-ID": {"<foo@bar.mail>"}}
|
||||||
|
// m.Attach("foo.jpg", gomail.SetHeader(h))
|
||||||
|
func SetHeader(h map[string][]string) FileSetting {
|
||||||
|
return func(f *file) {
|
||||||
|
for k, v := range h {
|
||||||
|
f.Header[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCopyFunc is a file setting to replace the function that runs when the
|
||||||
|
// message is sent.
|
||||||
|
// It should copy the content of the file to the io.Writer.
|
||||||
|
// The default copy function opens the file with the given filename, and copy
|
||||||
|
// its content to the io.Writer.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// m.Attach("foo.txt", gomail.SetCopyFunc(func(w io.Writer) error {
|
||||||
|
// _, err := w.Write([]byte("Content of foo.txt"))
|
||||||
|
// return err
|
||||||
|
// }))
|
||||||
|
func SetCopyFunc(f func(io.Writer) error) FileSetting {
|
||||||
|
return func(fi *file) {
|
||||||
|
fi.CopyFunc = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file {
|
||||||
|
f := &file{
|
||||||
|
Name: filepath.Base(name),
|
||||||
Header: make(map[string][]string),
|
Header: make(map[string][]string),
|
||||||
Copier: func(w io.Writer) error {
|
CopyFunc: func(w io.Writer) error {
|
||||||
h, err := os.Open(filename)
|
h, err := os.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -285,35 +323,33 @@ func NewFile(filename string) *File {
|
||||||
return h.Close()
|
return h.Close()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) setHeader(field string, value ...string) {
|
for _, s := range settings {
|
||||||
f.Header[field] = value
|
s(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
if list == nil {
|
||||||
|
return []*file{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(list, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach attaches the files to the email.
|
// Attach attaches the files to the email.
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// m.Attach(gomail.NewFile("/tmp/image.jpg"))
|
// m.Attach("/tmp/image.jpg")
|
||||||
func (m *Message) Attach(f ...*File) {
|
func (m *Message) Attach(filename string, settings ...FileSetting) {
|
||||||
if m.attachments == nil {
|
m.attachments = m.appendFile(m.attachments, filename, settings)
|
||||||
m.attachments = f
|
|
||||||
} else {
|
|
||||||
m.attachments = append(m.attachments, f...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embed embeds the images to the email.
|
// Embed embeds the images to the email.
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// m.Embed(gomail.NewFile("/tmp/image.jpg"))
|
// m.Embed("/tmp/image.jpg")
|
||||||
// m.SetBody("text/html", `<img src="cid:image.jpg" alt="My image" />`)
|
// m.SetBody("text/html", `<img src="cid:image.jpg" alt="My image" />`)
|
||||||
func (m *Message) Embed(image ...*File) {
|
func (m *Message) Embed(filename string, settings ...FileSetting) {
|
||||||
if m.embedded == nil {
|
m.embedded = m.appendFile(m.embedded, filename, settings)
|
||||||
m.embedded = image
|
|
||||||
} else {
|
|
||||||
m.embedded = append(m.embedded, image...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ func TestAttachmentOnly(t *testing.T) {
|
||||||
m := NewMessage()
|
m := NewMessage()
|
||||||
m.SetHeader("From", "from@example.com")
|
m.SetHeader("From", "from@example.com")
|
||||||
m.SetHeader("To", "to@example.com")
|
m.SetHeader("To", "to@example.com")
|
||||||
m.Attach(testFile("/tmp/test.pdf"))
|
m.Attach(mockCopyFile("/tmp/test.pdf"))
|
||||||
|
|
||||||
want := &message{
|
want := &message{
|
||||||
from: "from@example.com",
|
from: "from@example.com",
|
||||||
|
@ -217,7 +217,7 @@ func TestAttachment(t *testing.T) {
|
||||||
m.SetHeader("From", "from@example.com")
|
m.SetHeader("From", "from@example.com")
|
||||||
m.SetHeader("To", "to@example.com")
|
m.SetHeader("To", "to@example.com")
|
||||||
m.SetBody("text/plain", "Test")
|
m.SetBody("text/plain", "Test")
|
||||||
m.Attach(testFile("/tmp/test.pdf"))
|
m.Attach(mockCopyFile("/tmp/test.pdf"))
|
||||||
|
|
||||||
want := &message{
|
want := &message{
|
||||||
from: "from@example.com",
|
from: "from@example.com",
|
||||||
|
@ -247,8 +247,8 @@ func TestAttachmentsOnly(t *testing.T) {
|
||||||
m := NewMessage()
|
m := NewMessage()
|
||||||
m.SetHeader("From", "from@example.com")
|
m.SetHeader("From", "from@example.com")
|
||||||
m.SetHeader("To", "to@example.com")
|
m.SetHeader("To", "to@example.com")
|
||||||
m.Attach(testFile("/tmp/test.pdf"))
|
m.Attach(mockCopyFile("/tmp/test.pdf"))
|
||||||
m.Attach(testFile("/tmp/test.zip"))
|
m.Attach(mockCopyFile("/tmp/test.zip"))
|
||||||
|
|
||||||
want := &message{
|
want := &message{
|
||||||
from: "from@example.com",
|
from: "from@example.com",
|
||||||
|
@ -280,8 +280,8 @@ func TestAttachments(t *testing.T) {
|
||||||
m.SetHeader("From", "from@example.com")
|
m.SetHeader("From", "from@example.com")
|
||||||
m.SetHeader("To", "to@example.com")
|
m.SetHeader("To", "to@example.com")
|
||||||
m.SetBody("text/plain", "Test")
|
m.SetBody("text/plain", "Test")
|
||||||
m.Attach(testFile("/tmp/test.pdf"))
|
m.Attach(mockCopyFile("/tmp/test.pdf"))
|
||||||
m.Attach(testFile("/tmp/test.zip"))
|
m.Attach(mockCopyFile("/tmp/test.zip"))
|
||||||
|
|
||||||
want := &message{
|
want := &message{
|
||||||
from: "from@example.com",
|
from: "from@example.com",
|
||||||
|
@ -317,10 +317,8 @@ func TestEmbedded(t *testing.T) {
|
||||||
m := NewMessage()
|
m := NewMessage()
|
||||||
m.SetHeader("From", "from@example.com")
|
m.SetHeader("From", "from@example.com")
|
||||||
m.SetHeader("To", "to@example.com")
|
m.SetHeader("To", "to@example.com")
|
||||||
f := testFile("image1.jpg")
|
m.Embed(mockCopyFileWithHeader(m, "image1.jpg", map[string][]string{"Content-ID": {"<test-content-id>"}}))
|
||||||
f.Header["Content-ID"] = []string{"<test-content-id>"}
|
m.Embed(mockCopyFile("image2.jpg"))
|
||||||
m.Embed(f)
|
|
||||||
m.Embed(testFile("image2.jpg"))
|
|
||||||
m.SetBody("text/plain", "Test")
|
m.SetBody("text/plain", "Test")
|
||||||
|
|
||||||
want := &message{
|
want := &message{
|
||||||
|
@ -361,8 +359,8 @@ func TestFullMessage(t *testing.T) {
|
||||||
m.SetHeader("To", "to@example.com")
|
m.SetHeader("To", "to@example.com")
|
||||||
m.SetBody("text/plain", "¡Hola, señor!")
|
m.SetBody("text/plain", "¡Hola, señor!")
|
||||||
m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
|
m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
|
||||||
m.Attach(testFile("test.pdf"))
|
m.Attach(mockCopyFile("test.pdf"))
|
||||||
m.Embed(testFile("image.jpg"))
|
m.Embed(mockCopyFile("image.jpg"))
|
||||||
|
|
||||||
want := &message{
|
want := &message{
|
||||||
from: "from@example.com",
|
from: "from@example.com",
|
||||||
|
@ -591,13 +589,16 @@ func getBoundaries(t *testing.T, count int, m string) []string {
|
||||||
|
|
||||||
var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
|
var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
|
||||||
|
|
||||||
func testFile(name string) *File {
|
func mockCopyFile(name string) (string, FileSetting) {
|
||||||
f := NewFile(name)
|
return name, SetCopyFunc(func(w io.Writer) error {
|
||||||
f.Copier = func(w io.Writer) error {
|
_, err := w.Write([]byte("Content of " + filepath.Base(name)))
|
||||||
_, err := w.Write([]byte("Content of " + filepath.Base(f.Name)))
|
|
||||||
return err
|
return err
|
||||||
}
|
})
|
||||||
return f
|
}
|
||||||
|
|
||||||
|
func mockCopyFileWithHeader(m *Message, name string, h map[string][]string) (string, FileSetting, FileSetting) {
|
||||||
|
name, f := mockCopyFile(name)
|
||||||
|
return name, f, SetHeader(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkFull(b *testing.B) {
|
func BenchmarkFull(b *testing.B) {
|
||||||
|
@ -618,8 +619,8 @@ func BenchmarkFull(b *testing.B) {
|
||||||
})
|
})
|
||||||
m.SetBody("text/plain", "¡Hola, señor!")
|
m.SetBody("text/plain", "¡Hola, señor!")
|
||||||
m.AddAlternative("text/html", "<p>¡Hola, señor!</p>")
|
m.AddAlternative("text/html", "<p>¡Hola, señor!</p>")
|
||||||
m.Attach(testFile("benchmark.txt"))
|
m.Attach(mockCopyFile("benchmark.txt"))
|
||||||
m.Embed(testFile("benchmark.jpg"))
|
m.Embed(mockCopyFile("benchmark.jpg"))
|
||||||
|
|
||||||
if err := Send(discardFunc, m); err != nil {
|
if err := Send(discardFunc, m); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -104,7 +104,7 @@ func (w *messageWriter) closeMultipart() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *messageWriter) addFiles(files []*File, isAttachment bool) {
|
func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if _, ok := f.Header["Content-Type"]; !ok {
|
if _, ok := f.Header["Content-Type"]; !ok {
|
||||||
mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
|
mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
|
||||||
|
@ -134,7 +134,7 @@ func (w *messageWriter) addFiles(files []*File, isAttachment bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.writeHeaders(f.Header)
|
w.writeHeaders(f.Header)
|
||||||
w.writeBody(f.Copier, Base64)
|
w.writeBody(f.CopyFunc, Base64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue