Fix Attach/EmbedReader and implement Attach/EmbedReadSeeker

This PR addresses #110.
`Msg.AttachReader()` would not output the attached file after consecutive writes (e.g. a write to a file and then send via Client).

This PR fixes this behaviour by first reading the io.Reader into memory and then creating a new `bytes.Reader`, which does support seeking. In the writeFunc we then seek to position 0 after a successful `io.Copy`. This is probably not the most memory efficient way of handling this, but otherwise we'll have to break the `io.Reader` interface.

Additionally, a new way of attaching/embedding files has been added: `Msg.AttachReadSeeker()` and `Msg.EmbedReadSeeker()` which take a ´io.ReadSeeker` as argument instead. These two methods will skip the reading into memory and make use of the `Seek` method of the corresponding interface instead.
This commit is contained in:
Winni Neessen 2023-01-31 17:38:31 +01:00
parent cfaeb51bef
commit e66ad2be1a
Signed by: wneessen
GPG key ID: 385AC9889632126E

44
msg.go
View file

@ -719,6 +719,12 @@ func (m *Msg) AttachReader(n string, r io.Reader, o ...FileOption) {
m.attachments = m.appendFile(m.attachments, f, o...) m.attachments = m.appendFile(m.attachments, f, o...)
} }
// AttachReadSeeker adds an attachment File via io.ReadSeeker to the Msg
func (m *Msg) AttachReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
f := fileFromReadSeeker(n, r)
m.attachments = m.appendFile(m.attachments, f, o...)
}
// AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg // AttachHTMLTemplate adds the output of a html/template.Template pointer as File attachment to the Msg
func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error { func (m *Msg) AttachHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
f, err := fileFromHTMLTemplate(n, t, d) f, err := fileFromHTMLTemplate(n, t, d)
@ -767,6 +773,12 @@ func (m *Msg) EmbedReader(n string, r io.Reader, o ...FileOption) {
m.embeds = m.appendFile(m.embeds, f, o...) m.embeds = m.appendFile(m.embeds, f, o...)
} }
// EmbedReadSeeker adds an embedded File from an io.ReadSeeker to the Msg
func (m *Msg) EmbedReadSeeker(n string, r io.ReadSeeker, o ...FileOption) {
f := fileFromReadSeeker(n, r)
m.embeds = m.appendFile(m.embeds, f, o...)
}
// EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg // EmbedHTMLTemplate adds the output of a html/template.Template pointer as embedded File to the Msg
func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error { func (m *Msg) EmbedHTMLTemplate(n string, t *ht.Template, d interface{}, o ...FileOption) error {
f, err := fileFromHTMLTemplate(n, t, d) f, err := fileFromHTMLTemplate(n, t, d)
@ -1114,15 +1126,37 @@ func fileFromFS(n string) *File {
// fileFromReader returns a File pointer from a given io.Reader // fileFromReader returns a File pointer from a given io.Reader
func fileFromReader(n string, r io.Reader) *File { func fileFromReader(n string, r io.Reader) *File {
d, err := io.ReadAll(r)
if err != nil {
return &File{}
}
br := bytes.NewReader(d)
return &File{ return &File{
Name: filepath.Base(n), Name: n,
Header: make(map[string][]string), Header: make(map[string][]string),
Writer: func(w io.Writer) (int64, error) { Writer: func(w io.Writer) (int64, error) {
nb, err := io.Copy(w, r) rb, cerr := io.Copy(w, br)
if err != nil { if cerr != nil {
return nb, err return rb, cerr
} }
return nb, nil _, cerr = br.Seek(0, io.SeekStart)
return rb, cerr
},
}
}
// fileFromReadSeeker returns a File pointer from a given io.ReadSeeker
func fileFromReadSeeker(n string, r io.ReadSeeker) *File {
return &File{
Name: n,
Header: make(map[string][]string),
Writer: func(w io.Writer) (int64, error) {
rb, err := io.Copy(w, r)
if err != nil {
return rb, err
}
_, err = r.Seek(0, io.SeekStart)
return rb, err
}, },
} }
} }