diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..171b230 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Mac +.DS_Store + +# Test out +/test/out diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d24fb68 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: go +sudo: required + +matrix: + allow_failures: + - go: master + include: + # Supported versions of Go: https://golang.org/dl/ + - go: '1.11.x' + - go: '1.12.x' + - go: master + +go_import_path: github.com/yi-ge/unzip + +cache: + directories: + - $GOPATH/pkg + +before_install: + - echo "TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}" + +script: + - go test -v diff --git a/README.md b/README.md index f63d4fb..8580400 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ -# unzip -Golang *.zip decompress. +# Unzip + +Golang \*.zip decompress. + +[![Build Status](https://img.shields.io/travis/com/yi-ge/unzip/master.svg)](https://travis-ci.com/yi-ge/unzip) +[![GoDoc](https://godoc.org/github.com/yi-ge/unzip?status.svg)](https://godoc.org/github.com/yi-ge/unzip) + +Golang \*.tar.xz decompress. + +Fork from [https://github.com/artdarek/go-unzip](https://github.com/artdarek/go-unzip) and remove print, add support for Symlink. + +Thank artdarek. + +## Usage + +```golang +import "github.com/yi-ge/unzip" + +u := unzip.New(filePath, outDir) +err := u.Extract() +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..400c5fe --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/yi-ge/unzip + +go 1.12 diff --git a/test/a.txt b/test/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/b.txt b/test/b.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/test/b.txt @@ -0,0 +1 @@ +1 diff --git a/test/c.txt b/test/c.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/test/c.txt @@ -0,0 +1 @@ +2 diff --git a/test/d.txt b/test/d.txt new file mode 120000 index 0000000..3451cc9 --- /dev/null +++ b/test/d.txt @@ -0,0 +1 @@ +dir/d.txt \ No newline at end of file diff --git a/test/dir/c.txt b/test/dir/c.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/test/dir/c.txt @@ -0,0 +1 @@ +2 diff --git a/test/dir/d.txt b/test/dir/d.txt new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/test/dir/d.txt @@ -0,0 +1 @@ +3 diff --git a/test/dir/dir/e.txt b/test/dir/dir/e.txt new file mode 100644 index 0000000..bf0d87a --- /dev/null +++ b/test/dir/dir/e.txt @@ -0,0 +1 @@ +4 \ No newline at end of file diff --git a/test/dir/dir/f.txt b/test/dir/dir/f.txt new file mode 120000 index 0000000..7064d14 --- /dev/null +++ b/test/dir/dir/f.txt @@ -0,0 +1 @@ +../f.txt \ No newline at end of file diff --git a/test/dir/e.txt b/test/dir/e.txt new file mode 100644 index 0000000..bf0d87a --- /dev/null +++ b/test/dir/e.txt @@ -0,0 +1 @@ +4 \ No newline at end of file diff --git a/test/dir/f.txt b/test/dir/f.txt new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/test/dir/f.txt @@ -0,0 +1 @@ +5 \ No newline at end of file diff --git a/test/t.zip b/test/t.zip new file mode 100644 index 0000000..a3f2b56 Binary files /dev/null and b/test/t.zip differ diff --git a/unzip.go b/unzip.go new file mode 100644 index 0000000..8250090 --- /dev/null +++ b/unzip.go @@ -0,0 +1,103 @@ +package unzip + +import ( + "archive/zip" + "io" + "io/ioutil" + "os" + "path/filepath" +) + +// Unzip - struct +type Unzip struct { + Src string + Dest string +} + +// New - Create a new Unzip. +func New(src string, dest string) Unzip { + return Unzip{src, dest} +} + +func writeSymbolicLink(filePath string, targetPath string) error { + err := os.MkdirAll(filepath.Dir(filePath), 0755) + if err != nil { + return err + } + + err = os.Symlink(targetPath, filePath) + if err != nil { + return err + } + + return nil +} + +// Extract - Extract zip file. +func (uz Unzip) Extract() error { + r, err := zip.OpenReader(uz.Src) + if err != nil { + return err + } + defer func() { + if err := r.Close(); err != nil { + panic(err) + } + }() + + os.MkdirAll(uz.Dest, 0755) + + // Closure to address file descriptors issue with all the deferred .Close() methods + extractAndWriteFile := func(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() + + path := filepath.Join(uz.Dest, f.Name) + + if f.FileInfo().IsDir() { + os.MkdirAll(path, f.Mode()) + } else { + mode := f.FileHeader.Mode() + if mode&os.ModeType == os.ModeSymlink { + data, err := ioutil.ReadAll(rc) + if err != nil { + return err + } + writeSymbolicLink(path, string(data)) + } else { + os.MkdirAll(filepath.Dir(path), f.Mode()) + outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + if err := outFile.Close(); err != nil { + panic(err) + } + }() + + _, err = io.Copy(outFile, rc) + if err != nil { + return err + } + } + } + return nil + } + + for _, f := range r.File { + err := extractAndWriteFile(f) + if err != nil { + return err + } + } + + return nil +} diff --git a/unzip_test.go b/unzip_test.go new file mode 100644 index 0000000..bf3b4e0 --- /dev/null +++ b/unzip_test.go @@ -0,0 +1,299 @@ +package unzip + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "testing" +) + +func currentDir() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + return dir +} + +func testNullContent(t *testing.T) { + aFile, err := os.Open(path.Join(currentDir(), "./test/a.txt")) + if err != nil { + t.Fatal(err) + } + defer aFile.Close() + + aFileContent, err := ioutil.ReadAll(aFile) + if err != nil { + t.Fatal(err) + } + + aOutFile, err := os.Open(path.Join(currentDir(), "./test/out/a.txt")) + if err != nil { + t.Fatal(err) + } + defer aOutFile.Close() + + aOutFileContent, err := ioutil.ReadAll(aOutFile) + if err != nil { + t.Fatal(err) + } + + if string(aFileContent) != string(aOutFileContent) { + t.Fatal("Unzip file content error.") + } + + if string(aFileContent) != "" { + t.Fatal("Unzip file content error.") + } +} + +func testContentEqual(t *testing.T) { + bFile, err := os.Open(path.Join(currentDir(), "./test/b.txt")) + if err != nil { + t.Fatal(err) + } + defer bFile.Close() + + bFileContent, err := ioutil.ReadAll(bFile) + if err != nil { + t.Fatal(err) + } + + bOutFile, err := os.Open(path.Join(currentDir(), "./test/out/b.txt")) + if err != nil { + t.Fatal(err) + } + defer bOutFile.Close() + + bOutFileContent, err := ioutil.ReadAll(bOutFile) + if err != nil { + t.Fatal(err) + } + + if string(bFileContent) != string(bOutFileContent) { + t.Log(string(bFileContent)) + t.Log(string(bOutFileContent)) + t.Fatal("Unzip file content error.") + } +} + +func testLink(t *testing.T) { + cOutFile, err := os.OpenFile(path.Join(currentDir(), "./test/out/c.txt"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + t.Fatal(err) + } + defer cOutFile.Close() + + cOutFileContent, err := ioutil.ReadAll(cOutFile) + if err != nil { + t.Fatal(err) + } + + cOutLinkFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/c.txt")) + if err != nil { + t.Fatal(err) + } + defer cOutLinkFile.Close() + + cOutLinkFileContent, err := ioutil.ReadAll(cOutLinkFile) + if err != nil { + t.Fatal(err) + } + + if string(cOutFileContent) != string(cOutLinkFileContent) { + t.Fatal("Unzip file content error or link file content error.") + } + + t.Log("Old c.txt content:", string(cOutFileContent)) + t.Log("Change c.txt content.") + _, err = cOutFile.WriteString("123") + if err != nil { + t.Fatal(err) + } + + cOutFileNew, err := os.Open(path.Join(currentDir(), "./test/out/c.txt")) + if err != nil { + t.Fatal(err) + } + defer cOutFileNew.Close() + + cOutFileContentNew, err := ioutil.ReadAll(cOutFileNew) + if err != nil { + t.Fatal(err) + } + t.Log("New c.txt content:", string(cOutFileContentNew)) + + cOutLinkFileNew, err := os.Open(path.Join(currentDir(), "./test/out/dir/c.txt")) + if err != nil { + t.Fatal(err) + } + defer cOutLinkFileNew.Close() + + cOutLinkFileContentNew, err := ioutil.ReadAll(cOutLinkFileNew) + if err != nil { + t.Fatal(err) + } + + if string(cOutFileContentNew) != string(cOutLinkFileContentNew) { + t.Fatal("Unzip file link error.") + } +} + +func testSymlink(t *testing.T) { + targetURL, err := filepath.EvalSymlinks(path.Join(currentDir(), "./test/out/d.txt")) + if err != nil { + t.Fatal(err) + } + + t.Log("targetURL:", targetURL) + if targetURL != path.Join(currentDir(), "./test/out/dir/d.txt") { + t.Fatal("Unzip file Symlink error.") + } + + dOutSymlinkFile, err := os.Open(path.Join(currentDir(), "./test/out/d.txt")) + if err != nil { + t.Fatal(err) + } + defer dOutSymlinkFile.Close() + + dOutSymlinkFileContent, err := ioutil.ReadAll(dOutSymlinkFile) + if err != nil { + t.Fatal(err) + } + + dOutFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/d.txt")) + if err != nil { + t.Fatal(err) + } + defer dOutFile.Close() + + dOutFileContent, err := ioutil.ReadAll(dOutFile) + if err != nil { + t.Fatal(err) + } + + if string(dOutSymlinkFileContent) != string(dOutFileContent) { + t.Fatal("The content of symlink file is not equal to that of target file") + } +} + +func testLintInDir(t *testing.T) { + eOutFile, err := os.OpenFile(path.Join(currentDir(), "./test/out/dir/dir/e.txt"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + t.Fatal(err) + } + defer eOutFile.Close() + + eOutFileContent, err := ioutil.ReadAll(eOutFile) + if err != nil { + t.Fatal(err) + } + + eOutLinkFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/e.txt")) + if err != nil { + t.Fatal(err) + } + defer eOutLinkFile.Close() + + eOutLinkFileContent, err := ioutil.ReadAll(eOutLinkFile) + if err != nil { + t.Fatal(err) + } + + if string(eOutFileContent) != string(eOutLinkFileContent) { + t.Fatal("Unzip file content error or link file content error.") + } + + t.Log("Old dir/dir/e.txt content:", string(eOutFileContent)) + t.Log("Change dir/dir/e.txt content.") + _, err = eOutFile.WriteString("abcde") + if err != nil { + t.Fatal(err) + } + + eOutFileNew, err := os.Open(path.Join(currentDir(), "./test/out/dir/dir/e.txt")) + if err != nil { + t.Fatal(err) + } + defer eOutFileNew.Close() + + eOutFileContentNew, err := ioutil.ReadAll(eOutFileNew) + if err != nil { + t.Fatal(err) + } + t.Log("New dir/dir/e.txt content:", string(eOutFileContentNew)) + + eOutLinkFileNew, err := os.Open(path.Join(currentDir(), "./test/out/dir/e.txt")) + if err != nil { + t.Fatal(err) + } + defer eOutLinkFileNew.Close() + + eOutLinkFileContentNew, err := ioutil.ReadAll(eOutLinkFileNew) + if err != nil { + t.Fatal(err) + } + + if string(eOutFileContentNew) != string(eOutLinkFileContentNew) { + t.Fatal("Unzip file link error.") + } +} + +func testSymlinkInDir(t *testing.T) { + inDirTargetURL, err := filepath.EvalSymlinks(path.Join(currentDir(), "./test/out/dir/dir/f.txt")) + if err != nil { + t.Fatal(err) + } + t.Log("F outSymlinkFile link:", inDirTargetURL) + + if inDirTargetURL != path.Join(currentDir(), "./test/out/dir/f.txt") { + t.Fatal("Unzip file Symlink error.") + } + + fOutSymlinkFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/dir/f.txt")) + if err != nil { + t.Fatal(err) + } + defer fOutSymlinkFile.Close() + + fOutSymlinkFileContent, err := ioutil.ReadAll(fOutSymlinkFile) + if err != nil { + t.Fatal(err) + } + + fOutFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/f.txt")) + if err != nil { + t.Fatal(err) + } + defer fOutFile.Close() + + fOutFileContent, err := ioutil.ReadAll(fOutFile) + if err != nil { + t.Fatal(err) + } + + if string(fOutSymlinkFileContent) != string(fOutFileContent) { + t.Fatal("The content of symlink file is not equal to that of target file") + } +} + +func TestUnzip(t *testing.T) { + filePath := filepath.FromSlash(path.Join(currentDir(), "./test/t.zip")) + outDir := filepath.FromSlash(path.Join(currentDir(), "./test/out") + "/") + + os.RemoveAll(outDir) + + unzip := New(filePath, outDir) + err := unzip.Extract() + if err != nil { + t.Fatal(err) + } + + t.Run("Content is null", testNullContent) + t.Run("Content is equal", testContentEqual) + // t.Run("Link", testLink) + t.Run("Symlink", testSymlink) + // t.Run("Link in dir", testLintInDir) + t.Run("Symlink in dir", testSymlinkInDir) +}