Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Add Read and Write methods to Archiver interface that accept io.Reader/io.Writer. #43

Merged
merged 2 commits into from
Oct 12, 2017
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
8 changes: 6 additions & 2 deletions archiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import (
type Archiver interface {
// Match checks supported files
Match(filename string) bool
// Make makes an archive.
// Make makes an archive file on disk.
Make(destination string, sources []string) error
// Open extracts an archive.
// Open extracts an archive file on disk.
Open(source, destination string) error
// Write writes an archive to a Writer.
Write(output io.Writer, sources []string) error
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apicompat: breaking change members added

// Read reads an archive from a Reader.
Read(input io.Reader, destination string) error
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apicompat: breaking change members added

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good bot 🐕

}

// SupportedFormats contains all supported archive formats
Expand Down
80 changes: 57 additions & 23 deletions archiver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,83 @@ func TestArchiver(t *testing.T) {
if _, ok := ar.(rarFormat); ok {
t.Skip("not supported")
}
symmetricTest(t, name, ar)
testWriteRead(t, name, ar)
testMakeOpen(t, name, ar)
})
}
}

// symmetricTest performs a symmetric test by using ar.Make to make an archive
// testWriteRead performs a symmetric test by using ar.Write to generate an archive
// from the test corpus, then using ar.Read to extract the archive and comparing
// the contents to ensure they are equal.
func testWriteRead(t *testing.T, name string, ar Archiver) {
buf := new(bytes.Buffer)
tmp, err := ioutil.TempDir("", "archiver")
if err != nil {
t.Fatalf("[%s] %v", name, err)
}
defer os.RemoveAll(tmp)

// Test creating archive
err = ar.Write(buf, []string{"testdata"})
if err != nil {
t.Fatalf("[%s] writing archive: didn't expect an error, but got: %v", name, err)
}

// Test extracting archive
err = ar.Read(buf, tmp)
if err != nil {
t.Fatalf("[%s] reading archive: didn't expect an error, but got: %v", name, err)
}

// Check that what was extracted is what was compressed
symmetricTest(t, name, tmp)
}

// testMakeOpen performs a symmetric test by using ar.Make to make an archive
// from the test corpus, then using ar.Open to open the archive and comparing
// the contents to ensure they are equal.
func symmetricTest(t *testing.T, name string, ar Archiver) {
func testMakeOpen(t *testing.T, name string, ar Archiver) {
tmp, err := ioutil.TempDir("", "archiver")
if err != nil {
t.Fatal(err)
t.Fatalf("[%s] %v", name, err)
}
defer os.RemoveAll(tmp)

// Test creating archive
outfile := filepath.Join(tmp, "test-"+name)
err = ar.Make(outfile, []string{"testdata"})
if err != nil {
t.Fatalf("making archive: didn't expect an error, but got: %v", err)
t.Fatalf("[%s] making archive: didn't expect an error, but got: %v", name, err)
}

if !ar.Match(outfile) {
t.Fatalf("identifying format should be 'true', but got 'false'")
t.Fatalf("[%s] identifying format should be 'true', but got 'false'", name)
}

var expectedFileCount int
filepath.Walk("testdata", func(fpath string, info os.FileInfo, err error) error {
expectedFileCount++
return nil
})

// Test extracting archive
dest := filepath.Join(tmp, "extraction_test")
os.Mkdir(dest, 0755)
err = ar.Open(outfile, dest)
if err != nil {
t.Fatalf("extracting archive [%s -> %s]: didn't expect an error, but got: %v", outfile, dest, err)
t.Fatalf("[%s] extracting archive [%s -> %s]: didn't expect an error, but got: %v", name, outfile, dest, err)
}

// Check that what was extracted is what was compressed
symmetricTest(t, name, dest)
}

// symmetricTest compares the contents of a destination directory to the contents
// of the test corpus and tests that they are equal.
func symmetricTest(t *testing.T, name, dest string) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unparam: name is unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by next commit.

var expectedFileCount int
filepath.Walk("testdata", func(fpath string, info os.FileInfo, err error) error {
expectedFileCount++
return nil
})

// If outputs equals inputs, we're good; traverse output files
// and compare file names, file contents, and file count.

var actualFileCount int
filepath.Walk(dest, func(fpath string, info os.FileInfo, err error) error {
if fpath == dest {
Expand All @@ -69,51 +103,51 @@ func symmetricTest(t *testing.T, name string, ar Archiver) {

origPath, err := filepath.Rel(dest, fpath)
if err != nil {
t.Fatalf("%s: Error inducing original file path: %v", fpath, err)
t.Fatalf("[%s] %s: Error inducing original file path: %v", name, fpath, err)
}

if info.IsDir() {
// stat dir instead of read file
_, err = os.Stat(origPath)
if err != nil {
t.Fatalf("%s: Couldn't stat original directory (%s): %v",
t.Fatalf("[%s] %s: Couldn't stat original directory (%s): %v", name,
fpath, origPath, err)
}
return nil
}

expectedFileInfo, err := os.Stat(origPath)
if err != nil {
t.Fatalf("%s: Error obtaining original file info: %v", fpath, err)
t.Fatalf("[%s] %s: Error obtaining original file info: %v", name, fpath, err)
}
expected, err := ioutil.ReadFile(origPath)
if err != nil {
t.Fatalf("%s: Couldn't open original file (%s) from disk: %v",
t.Fatalf("[%s] %s: Couldn't open original file (%s) from disk: %v", name,
fpath, origPath, err)
}

actualFileInfo, err := os.Stat(fpath)
if err != nil {
t.Fatalf("%s: Error obtaining actual file info: %v", fpath, err)
t.Fatalf("[%s] %s: Error obtaining actual file info: %v", name, fpath, err)
}
actual, err := ioutil.ReadFile(fpath)
if err != nil {
t.Fatalf("%s: Couldn't open new file from disk: %v", fpath, err)
t.Fatalf("[%s] %s: Couldn't open new file from disk: %v", name, fpath, err)
}

if actualFileInfo.Mode() != expectedFileInfo.Mode() {
t.Fatalf("%s: File mode differed between on disk and compressed",
t.Fatalf("[%s] %s: File mode differed between on disk and compressed", name,
expectedFileInfo.Mode().String()+" : "+actualFileInfo.Mode().String())
}
if !bytes.Equal(expected, actual) {
t.Fatalf("%s: File contents differed between on disk and compressed", origPath)
t.Fatalf("[%s] %s: File contents differed between on disk and compressed", name, origPath)
}

return nil
})

if got, want := actualFileCount, expectedFileCount; got != want {
t.Fatalf("Expected %d resulting files, got %d", want, got)
t.Fatalf("[%s] Expected %d resulting files, got %d", name, want, got)
}
}

Expand Down
28 changes: 23 additions & 5 deletions rar.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,27 @@ func isRar(rarPath string) bool {
bytes.Equal(buf, []byte("Rar!\x1a\x07\x01\x00")) // ver 5.0
}

// Write outputs a .rar archive, but this is not implemented because
// RAR is a proprietary format. It is here only for symmetry with
// the other archive formats in this package.
func (rarFormat) Write(output io.Writer, filePaths []string) error {
return fmt.Errorf("write: RAR not implemented (proprietary format)")
}

// Make makes a .rar archive, but this is not implemented because
// RAR is a proprietary format. It is here only for symmetry with
// the other archive formats in this package.
func (rarFormat) Make(rarPath string, filePaths []string) error {
return fmt.Errorf("make %s: RAR not implemented (proprietary format)", rarPath)
}

// Open extracts the RAR file at source and puts the contents
// Read extracts the RAR file read from input and puts the contents
// into destination.
func (rarFormat) Open(source, destination string) error {
rr, err := rardecode.OpenReader(source, "")
func (rarFormat) Read(input io.Reader, destination string) error {
rr, err := rardecode.NewReader(input, "")
if err != nil {
return fmt.Errorf("%s: failed to create reader: %v", source, err)
return fmt.Errorf("read: failed to create reader: %v", err)
}
defer rr.Close()

for {
header, err := rr.Next()
Expand Down Expand Up @@ -89,3 +95,15 @@ func (rarFormat) Open(source, destination string) error {

return nil
}

// Open extracts the RAR file at source and puts the contents
// into destination.
func (rarFormat) Open(source, destination string) error {
rf, err := os.Open(source)
if err != nil {
return fmt.Errorf("%s: failed to open file: %v", source, err)
}
defer rf.Close()

return Rar.Read(rf, destination)
}
25 changes: 22 additions & 3 deletions tar.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ func hasTarHeader(buf []byte) bool {
return true
}

// Write outputs a .tar file to a Writer containing the
// contents of files listed in filePaths. File paths can
// be those of regular files or directories. Regular
// files are stored at the 'root' of the archive, and
// directories are recursively added.
func (tarFormat) Write(output io.Writer, filePaths []string) error {
return writeTar(filePaths, output, "")
}

// Make creates a .tar file at tarPath containing the
// contents of files listed in filePaths. File paths can
// be those of regular files or directories. Regular
Expand All @@ -91,10 +100,14 @@ func (tarFormat) Make(tarPath string, filePaths []string) error {
}
defer out.Close()

tarWriter := tar.NewWriter(out)
return writeTar(filePaths, out, tarPath)
}

func writeTar(filePaths []string, output io.Writer, dest string) error {
tarWriter := tar.NewWriter(output)
defer tarWriter.Close()

return tarball(filePaths, tarWriter, tarPath)
return tarball(filePaths, tarWriter, dest)
}

// tarball writes all files listed in filePaths into tarWriter, which is
Expand Down Expand Up @@ -170,6 +183,12 @@ func tarFile(tarWriter *tar.Writer, source, dest string) error {
})
}

// Read untars a .tar file read from a Reader and puts
// the contents into destination.
func (tarFormat) Read(input io.Reader, destination string) error {
return untar(tar.NewReader(input), destination)
}

// Open untars source and puts the contents into destination.
func (tarFormat) Open(source, destination string) error {
f, err := os.Open(source)
Expand All @@ -178,7 +197,7 @@ func (tarFormat) Open(source, destination string) error {
}
defer f.Close()

return untar(tar.NewReader(f), destination)
return Tar.Read(f, destination)
}

// untar un-tarballs the contents of tr into destination.
Expand Down
44 changes: 30 additions & 14 deletions tarbz2.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package archiver

import (
"archive/tar"
"fmt"
"io"
"os"
"strings"

Expand Down Expand Up @@ -48,6 +48,15 @@ func isTarBz2(tarbz2Path string) bool {
return hasTarHeader(buf)
}

// Write outputs a .tar.bz2 file to a Writer containing
// the contents of files listed in filePaths. File paths
// can be those of regular files or directories. Regular
// files are stored at the 'root' of the archive, and
// directories are recursively added.
func (tarBz2Format) Write(output io.Writer, filePaths []string) error {
return writeTarBz2(filePaths, output, "")
}

// Make creates a .tar.bz2 file at tarbz2Path containing
// the contents of files listed in filePaths. File paths
// can be those of regular files or directories. Regular
Expand All @@ -60,16 +69,29 @@ func (tarBz2Format) Make(tarbz2Path string, filePaths []string) error {
}
defer out.Close()

bz2Writer, err := bzip2.NewWriter(out, nil)
return writeTarBz2(filePaths, out, tarbz2Path)
}

func writeTarBz2(filePaths []string, output io.Writer, dest string) error {
bz2w, err := bzip2.NewWriter(output, nil)
if err != nil {
return fmt.Errorf("error compressing %s: %v", tarbz2Path, err)
return fmt.Errorf("error compressing bzip2: %v", err)
}
defer bz2Writer.Close()
defer bz2w.Close()

return writeTar(filePaths, bz2w, dest)
}

tarWriter := tar.NewWriter(bz2Writer)
defer tarWriter.Close()
// Read untars a .tar.bz2 file read from a Reader and decompresses
// the contents into destination.
func (tarBz2Format) Read(input io.Reader, destination string) error {
bz2r, err := bzip2.NewReader(input, nil)
if err != nil {
return fmt.Errorf("error decompressing bzip2: %v", err)
}
defer bz2r.Close()

return tarball(filePaths, tarWriter, tarbz2Path)
return Tar.Read(bz2r, destination)
}

// Open untars source and decompresses the contents into destination.
Expand All @@ -80,11 +102,5 @@ func (tarBz2Format) Open(source, destination string) error {
}
defer f.Close()

bz2r, err := bzip2.NewReader(f, nil)
if err != nil {
return fmt.Errorf("error decompressing %s: %v", source, err)
}
defer bz2r.Close()

return untar(tar.NewReader(bz2r), destination)
return TarBz2.Read(f, destination)
}
Loading