Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: stream write to zip directly #863

Merged
merged 2 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 48 additions & 26 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,55 @@ func (f *File) Write(w io.Writer) error {

// WriteTo implements io.WriterTo to write the file.
func (f *File) WriteTo(w io.Writer) (int64, error) {
buf, err := f.WriteToBuffer()
if err != nil {
if f.options != nil && f.options.Password != "" {
buf, err := f.WriteToBuffer()
if err != nil {
return 0, err
}
return buf.WriteTo(w)
}
if err := f.writeDirectToWriter(w); err != nil {
return 0, err
}
return buf.WriteTo(w)
return 0, nil
}

// WriteToBuffer provides a function to get bytes.Buffer from the saved file.
// WriteToBuffer provides a function to get bytes.Buffer from the saved file. And it allocate space in memory. Be careful when the file size is large.
func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
zw := zip.NewWriter(buf)

if err := f.writeToZip(zw); err != nil {
return buf, zw.Close()
}

if f.options != nil && f.options.Password != "" {
if err := zw.Close(); err != nil {
return buf, err
}
b, err := Encrypt(buf.Bytes(), f.options)
if err != nil {
return buf, err
}
buf.Reset()
buf.Write(b)
return buf, nil
}
return buf, zw.Close()
}

// writeDirectToWriter provides a function to write to io.Writer.
func (f *File) writeDirectToWriter(w io.Writer) error {
zw := zip.NewWriter(w)
if err := f.writeToZip(zw); err != nil {
zw.Close()
return err
}
return zw.Close()
}

// writeToZip provides a function to write to zip.Writer
func (f *File) writeToZip(zw *zip.Writer) error {
f.calcChainWriter()
f.commentsWriter()
f.contentTypesWriter()
Expand All @@ -112,19 +150,17 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
for path, stream := range f.streams {
fi, err := zw.Create(path)
if err != nil {
zw.Close()
return buf, err
return err
}
var from io.Reader
from, err = stream.rawData.Reader()
if err != nil {
stream.rawData.Close()
return buf, err
return err
}
_, err = io.Copy(fi, from)
if err != nil {
zw.Close()
return buf, err
return err
}
stream.rawData.Close()
}
Expand All @@ -135,27 +171,13 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
}
fi, err := zw.Create(path)
if err != nil {
zw.Close()
return buf, err
return err
}
_, err = fi.Write(content)
if err != nil {
zw.Close()
return buf, err
return err
}
}

if f.options != nil && f.options.Password != "" {
if err := zw.Close(); err != nil {
return buf, err
}
b, err := Encrypt(buf.Bytes(), f.options)
if err != nil {
return buf, err
}
buf.Reset()
buf.Write(b)
return buf, nil
}
return buf, zw.Close()
return nil
}
43 changes: 32 additions & 11 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package excelize
import (
"bufio"
"bytes"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -33,16 +34,36 @@ func BenchmarkWrite(b *testing.B) {
}

func TestWriteTo(t *testing.T) {
f := File{}
buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte)
f.XLSX["/d/"] = []byte("s")
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: write to directory")
delete(f.XLSX, "/d/")
// Test WriteToBuffer err
{
f := File{}
buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte)
f.XLSX["/d/"] = []byte("s")
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: write to directory")
delete(f.XLSX, "/d/")
}
// Test file path overflow
const maxUint16 = 1<<16 - 1
f.XLSX[strings.Repeat("s", maxUint16+1)] = nil
_, err = f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long")
{
f := File{}
buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte)
const maxUint16 = 1<<16 - 1
f.XLSX[strings.Repeat("s", maxUint16+1)] = nil
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long")
}
// Test StreamsWriter err
{
f := File{}
buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte)
f.XLSX["s"] = nil
f.streams = make(map[string]*StreamWriter)
file, _ := os.Open("123")
f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}}
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.Nil(t, err)
}
}