- Initial commit.

This commit is contained in:
Naoriel Sa' Rocí 2024-05-31 19:52:04 +02:00
commit 27d3e52b46
16 changed files with 1029 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.log
.vscode/

8
CHANGELOG.md Normal file
View File

@ -0,0 +1,8 @@
# git.sa-roci.de/oss/go_zipper Release notes
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v. 0.0.1
- Initial Release.

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Sa Rocí Solutions
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# git.sa-roci.de/oss/go_zipper
An extensible module for file (de-)compression.
Includes sub-packages for gzip and zip (de-)compression.

184
compress.go Normal file
View File

@ -0,0 +1,184 @@
package zipper
import (
"fmt"
"io"
"os"
)
type Writer interface {
Flush() error
Close() error
}
type Compressor interface {
Compress(fileToZip *os.File, pathInArchive string) (err error)
OpenWriter(zipFile *os.File) (err error)
MultiFileSupported() bool
}
// zipper defines a struct for a Zip-file writer
type zipper struct {
filename string
zipFile *os.File
zipWriter Writer
isOpen bool
compressor Compressor
canCompress bool
}
type Zipper interface {
io.Closer
AddZipFilesPathed(files []string, pathInArchive, pathInFileSystem string) error
AddZipFiles(files []string, pathInArchive string) error
AddZipFilesPathedSimple(files []string, pathInFileSystem string) error
AddZipFilesSimple(files []string) error
AddFileToZipPathed(filename, pathInArchive, pathInFileSystem string) error
AddFileToZip(filename, pathInArchive string) error
AddFileToZipPathedSimple(filename, pathInFileSystem string) error
AddFileToZipSimple(filename string) error
SetCompressor(compressor Compressor) error
SetWriter(writer Writer)
GetWriter() (writer Writer)
}
// NewBaseZipper makes a new Zipper interface missing the compressor.
func NewBaseZipper(archiveFilename string) Zipper {
return &zipper{filename: archiveFilename, isOpen: false, canCompress: true}
}
func (zipper *zipper) SetCompressor(compressor Compressor) (err error) {
if compressor != nil {
if zipper.compressor == nil {
zipper.compressor = compressor
} else {
err = fmt.Errorf("compressor already defined")
}
} else {
err = fmt.Errorf("undefined compressor")
}
return
}
func (zipper *zipper) SetWriter(writer Writer) {
zipper.zipWriter = writer
}
func (zipper *zipper) GetWriter() Writer {
return zipper.zipWriter
}
// Close implements the Closer interface
func (zipper *zipper) Close() (err error) {
if zipper.isOpen {
if nil != zipper.zipWriter {
zipper.zipWriter.Flush()
if err = zipper.zipWriter.Close(); err == nil {
zipper.zipWriter = nil
}
}
if nil == err && nil != zipper.zipFile {
zipper.zipFile.Sync()
if err = zipper.zipFile.Close(); err == nil {
zipper.zipFile = nil
}
}
if err == nil {
zipper.isOpen = false
}
}
return
}
// AddZipFilesPathed compresses one or many files into the current zip archive file.
// Param 1: files is a list of files to add to the zip.
// Param 2: pathInArchive is subfolder definition within the zip, where the files are to be situated.
// Param 3: pathInFileSystem is the subfolder definition within the file system, where the file may be found.
func (zipper *zipper) AddZipFilesPathed(files []string, pathInArchive, pathInFileSystem string) (err error) {
// Add files to zip
for _, file := range files {
if err = zipper.AddFileToZipPathed(file, pathInArchive, pathInFileSystem); err != nil {
break
}
}
return err
}
// AddZipFiles compresses one or many files into the current zip archive file.
// Param 1: files is a list of files to add to the zip.
// Param 2: pathInArchive is subfolder definition within the zip, where the files are to be situated.
func (zipper *zipper) AddZipFiles(files []string, pathInArchive string) error {
return zipper.AddZipFilesPathed(files, pathInArchive, "")
}
// AddZipFilesPathedSimple compresses one or many files into the current zip archive file.
// Param 1: files is a list of files to add to the zip.
// Param 2: pathInFileSystem is the subfolder definition within the file system, where the file may be found.
func (zipper *zipper) AddZipFilesPathedSimple(files []string, pathInFileSystem string) error {
return zipper.AddZipFilesPathed(files, "", pathInFileSystem)
}
// AddZipFilesSimple compresses one or many files into the current zip archive file.
// Param: files is a list of files to add to the zip.
func (zipper *zipper) AddZipFilesSimple(files []string) error {
return zipper.AddZipFilesPathed(files, "", "")
}
// AddFileToZip compresses a single file into the current zip archive file.
// Param 1: filename reflects the path of the file to add to the zip within the file system.
// Param 2: pathInArchive is the subfolder definition within the zip, where the file is to be situated.
func (zipper *zipper) AddFileToZip(filename, pathInArchive string) error {
return zipper.AddFileToZipPathed(filename, pathInArchive, "")
}
// AddFileToZipPathedSimple compresses a single file into the current zip archive file.
// Param 1: filename reflects the path of the file to add to the zip within the file system.
// Param 2: pathInFileSystem is the subfolder definition within the file system, where the file may be found.
func (zipper *zipper) AddFileToZipPathedSimple(filename, pathInFileSystem string) error {
return zipper.AddFileToZipPathed(filename, "", pathInFileSystem)
}
// AddFileToZipSimple compresses a single file into the current zip archive file.
// Param: filename reflects the path of the file to add to the zip within the file system.
func (zipper *zipper) AddFileToZipSimple(filename string) error {
return zipper.AddFileToZipPathed(filename, "", "")
}
// AddFileToZipPathed compresses a single file into the current zip archive file.
// Param 1: filename reflects the path of the file to add to the zip within the file system.
// Param 2: pathInArchive is the subfolder definition within the zip, where the file is to be situated.
// Param 3: pathInFileSystem is the subfolder definition within the file system, where the file may be found.
func (zipper *zipper) AddFileToZipPathed(filename, pathInArchive, pathInFileSystem string) (err error) {
if zipper.compressor == nil {
return fmt.Errorf("unable to initialise compressor")
}
if !zipper.isOpen {
if err = MakeSurePathExists(zipper.filename, 0644); err == nil {
if zipper.zipFile, err = os.Create(zipper.filename); err == nil {
err = zipper.compressor.OpenWriter(zipper.zipFile)
zipper.isOpen = true
}
}
}
if nil != err {
return err
}
if !zipper.canCompress {
return fmt.Errorf("no further compression is supported")
}
var fileToZip *os.File
if fileToZip, err = os.Open(PrependPath(filename, pathInFileSystem)); err != nil {
return err
}
defer fileToZip.Close()
if err = zipper.compressor.Compress(fileToZip, pathInArchive); err == nil {
if !zipper.compressor.MultiFileSupported() {
zipper.canCompress = false
}
}
return err
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.sa-roci.de/oss/go_zipper
go 1.20

78
gzip/compress.go Normal file
View File

@ -0,0 +1,78 @@
package gzip
import (
"compress/gzip"
"fmt"
"io"
"io/fs"
"os"
"strings"
zipper "git.sa-roci.de/oss/go_zipper"
)
const ArchiveExt = ".gz"
// NewZipper makes a new Zipper.
func NewGZipper(gzipFilename string, multiFile bool) zipper.Zipper {
if !strings.HasSuffix(strings.ToLower(gzipFilename), ArchiveExt) {
gzipFilename += ArchiveExt
}
gzipper := gZipper{
Zipper: zipper.NewBaseZipper(gzipFilename),
multiFile: multiFile,
}
gzipper.SetCompressor(&gzipper)
return gzipper
}
type gZipper struct {
zipper.Zipper
output io.Writer
multiFile bool
}
func (gz *gZipper) OpenWriter(zipFile *os.File) error {
gz.SetWriter(gzip.NewWriter(zipFile))
gz.output = zipFile
return nil
}
func (gz *gZipper) MultiFileSupported() bool {
return gz.multiFile
}
func (gz *gZipper) Compress(fileToZip *os.File, pathInArchive string) (err error) {
// Get the file information
var info fs.FileInfo
if info, err = fileToZip.Stat(); err != nil {
return err
}
filename := info.Name()
if writer, ok := gz.GetWriter().(*gzip.Writer); ok {
writer.ModTime = info.ModTime()
// Using FileInfoHeader() above only uses the basename of the file. If we want
// to preserve the folder structure we can overwrite this with the full path.
if len(pathInArchive) > 0 || strings.Contains(filename, "/") || strings.Contains(filename, "\\") {
writer.Name = zipper.PrependPath(filename, pathInArchive)
} else {
writer.Name = filename
}
if _, err = io.Copy(writer, fileToZip); err == nil {
flushErr := writer.Flush()
if err = writer.Close(); err != nil {
err = flushErr
} else {
writer.Reset(gz.output)
}
}
} else {
err = fmt.Errorf("unable to access compressor")
}
return
}

92
gzip/gzip_test.go Normal file
View File

@ -0,0 +1,92 @@
package gzip
import (
"io/fs"
"os"
"path/filepath"
"testing"
)
const (
content = "test-content"
)
func TestZipper(t *testing.T) {
archiveFolder := "testdata"
archiveFilename := archiveFolder + "/dummy"
testFileName := "testZipper.data"
if os.WriteFile(testFileName, []byte(content), 0644) == nil {
defer os.Remove(testFileName)
}
z := NewGZipper(archiveFilename, true)
if z.GetWriter() != nil {
t.Log("Found unexpected writer")
t.Fail()
}
// Test adding files
if z.AddZipFiles([]string{testFileName}, "testfolder") != nil {
t.Log("Unable to add a single file")
t.Fail()
}
// Test adding more files than allowed
if z.AddFileToZip(testFileName, "testfolder/more") != nil {
t.Log("Unexpectedly failed in adding more than one file")
t.Fail()
}
z.Close()
uz := NewGUnzipper(archiveFilename + ArchiveExt)
if uz.ExtractFiles("") != nil {
t.Log("Failed to extract files")
t.Fail()
} else {
if data, err := os.ReadFile("testfolder/" + testFileName); err != nil {
t.Log("Expected extracted file #1 is missing")
t.Fail()
} else if string(data) != content {
t.Logf("Unexpected extracted file #1 content: '%s'", string(data))
t.Fail()
}
if data, err := os.ReadFile("testfolder/more/" + testFileName); err != nil {
t.Log("Expected extracted file #2 is missing")
t.Fail()
} else if string(data) != content {
t.Logf("Unexpected extracted file #2 content: '%s'", string(data))
t.Fail()
}
}
// Clean up
removeRecursive("testfolder")
os.Remove(archiveFilename + ArchiveExt)
os.Remove(archiveFolder)
}
func removeRecursive(path string) {
filepath.Walk(path, deleteFiles)
filepath.WalkDir(path, deleteFolders)
filepath.WalkDir(path, deleteFolders)
filepath.WalkDir(path, deleteFolders)
}
func deleteFiles(path string, info fs.FileInfo, errIn error) (err error) {
if errIn != nil {
return
}
if !info.IsDir() {
err = os.Remove(path)
}
return err
}
func deleteFolders(path string, info fs.DirEntry, errIn error) (err error) {
if errIn != nil {
return
}
os.Remove(path)
return
}

66
gzip/uncompress.go Normal file
View File

@ -0,0 +1,66 @@
package gzip
import (
"bytes"
"compress/gzip"
"io"
"os"
zipper "git.sa-roci.de/oss/go_zipper"
)
// NewGUnzipper makes a new Unzipper.
func NewGUnzipper(zipFilename string) zipper.Unzipper {
guz := gUnzipper{
Unzipper: zipper.NewBaseUnzipper(zipFilename),
}
guz.SetDecompressor(&guz)
return guz
}
type gUnzipper struct {
zipper.Unzipper
buffer bytes.Buffer
}
func (guz *gUnzipper) OpenReader(zipFile *os.File) (err error) {
var reader *gzip.Reader
guz.buffer = bytes.Buffer{}
if _, err = io.Copy(&guz.buffer, zipFile); err == nil {
if reader, err = gzip.NewReader(&guz.buffer); err == nil {
guz.SetReader(reader)
}
}
return
}
func (guz *gUnzipper) Decompress(targetFolderPathInFileSystem string) (err error) {
if reader, ok := guz.GetReader().(*gzip.Reader); ok {
if err = zipper.MakeSurePathExists(targetFolderPathInFileSystem+"/dummy.file", 0644); err == nil {
for {
if reader.Name == "" {
break
}
reader.Multistream(false)
outputPath := zipper.PrependPath(reader.Name, targetFolderPathInFileSystem)
if err = zipper.MakeSurePathExists(outputPath, 0644); err == nil {
var dstFile *os.File
if dstFile, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err == nil {
defer dstFile.Close()
if _, err = io.Copy(dstFile, reader); err == nil {
if err = reader.Reset(&guz.buffer); err == io.EOF {
err = nil
break
}
}
}
}
if err != nil {
break
}
}
}
}
return
}

28
helper.go Normal file
View File

@ -0,0 +1,28 @@
package zipper
import (
"os"
"strings"
)
func PrependPath(filename, path string) string {
if len(path) == 0 {
return strings.ReplaceAll(strings.ReplaceAll(filename, "\\", "/"), "//", "/")
}
return strings.ReplaceAll(strings.ReplaceAll(path+"/"+filename, "\\", "/"), "//", "/")
}
// MakeSurePathExists creates all missing parent directories, given a specific file path.
func MakeSurePathExists(filepath string, perm os.FileMode) error {
i := len(filepath)
for i > 0 && !os.IsPathSeparator(filepath[i-1]) { // Scan backward over element.
i--
}
var err error
if i > 1 {
// Create parent.
err = os.MkdirAll(filepath[:i-1], perm)
}
return err
}

99
uncompress.go Normal file
View File

@ -0,0 +1,99 @@
package zipper
import (
"fmt"
"io"
"os"
)
// unzipper defines a struct for a Zip-file writer
type unzipper struct {
filename string
zipFile *os.File
zipReader io.Closer
isOpen bool
decompressor Decompressor
}
type Decompressor interface {
Decompress(targetFolderPathInFileSystem string) (err error)
OpenReader(zipFile *os.File) (err error)
}
type Unzipper interface {
io.Closer
ExtractFiles(targetFolderPathInFileSystem string) error
SetDecompressor(decompressor Decompressor) error
SetReader(reader io.Closer)
GetReader() (reader io.Closer)
}
// NewBaseUnzipper makes a new Unzipper interface missing the decompressor.
func NewBaseUnzipper(archiveFilename string) Unzipper {
return &unzipper{filename: archiveFilename, isOpen: false}
}
func (unzipper *unzipper) SetDecompressor(decompressor Decompressor) (err error) {
if decompressor != nil {
if unzipper.decompressor == nil {
unzipper.decompressor = decompressor
} else {
err = fmt.Errorf("decompressor already defined")
}
} else {
err = fmt.Errorf("undefined decompressor")
}
return
}
func (unzipper *unzipper) SetReader(reader io.Closer) {
unzipper.zipReader = reader
}
func (unzipper *unzipper) GetReader() io.Closer {
return unzipper.zipReader
}
// Close implements the Closer interface
func (unzipper *unzipper) Close() (err error) {
if unzipper.isOpen {
if nil != unzipper.zipReader {
if err = unzipper.zipReader.Close(); err == nil {
unzipper.zipReader = nil
}
}
if nil == err && nil != unzipper.zipFile {
if err = unzipper.zipFile.Close(); err == nil {
unzipper.zipFile = nil
}
}
if err == nil {
unzipper.isOpen = false
}
}
return
}
func (unzipper *unzipper) ExtractFiles(targetFolderPathInFileSystem string) (err error) {
if unzipper.decompressor == nil {
return fmt.Errorf("unable to initialise decompressor")
}
if !unzipper.isOpen {
if unzipper.zipFile, err = os.Open(unzipper.filename); err == nil {
err = unzipper.decompressor.OpenReader(unzipper.zipFile)
unzipper.isOpen = true
defer func() {
errClose := unzipper.Close()
if err == nil {
err = errClose
}
}()
}
}
if err == nil {
err = unzipper.decompressor.Decompress(targetFolderPathInFileSystem)
}
return err
}

77
zip/compress.go Normal file
View File

@ -0,0 +1,77 @@
package zip
import (
"archive/zip"
"fmt"
"io"
"io/fs"
"os"
"strings"
"git.sa-roci.de/oss/go_zipper"
)
const ArchiveExt = ".zip"
// NewZipper makes a new Zipper.
func NewZipper(zipFilename string) zipper.Zipper {
if !strings.HasSuffix(strings.ToLower(zipFilename), ArchiveExt) {
zipFilename += ArchiveExt
}
z := zZipper{
Zipper: zipper.NewBaseZipper(zipFilename),
}
z.SetCompressor(z)
return z
}
type zZipper struct {
zipper.Zipper
}
func (z zZipper) OpenWriter(zipFile *os.File) error {
z.SetWriter(zip.NewWriter(zipFile))
return nil
}
func (z zZipper) MultiFileSupported() bool {
return true
}
func (z zZipper) Compress(fileToZip *os.File, pathInArchive string) (err error) {
// Get the file information
var info fs.FileInfo
if info, err = fileToZip.Stat(); err != nil {
return err
}
var header *zip.FileHeader
if header, err = zip.FileInfoHeader(info); err != nil {
return err
}
filename := info.Name()
// Using FileInfoHeader() above only uses the basename of the file. If we want
// to preserve the folder structure we can overwrite this with the full path.
if len(pathInArchive) > 0 || strings.Contains(filename, "/") || strings.Contains(filename, "\\") {
header.Name = zipper.PrependPath(filename, pathInArchive)
}
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants
header.Method = zip.Deflate
if zipWriter, ok := z.GetWriter().(*zip.Writer); ok {
var writer io.Writer
if writer, err = zipWriter.CreateHeader(header); err != nil {
return err
}
_, err = io.Copy(writer, fileToZip)
} else {
err = fmt.Errorf("unable to access compressor")
}
return
}

60
zip/uncompress.go Normal file
View File

@ -0,0 +1,60 @@
package zip
import (
"archive/zip"
"io"
"os"
"git.sa-roci.de/oss/go_zipper"
)
// NewUnzipper makes a new Unzipper.
func NewUnzipper(zipFilename string) zipper.Unzipper {
uz := zUnzipper{
Unzipper: zipper.NewBaseUnzipper(zipFilename),
}
uz.SetDecompressor(uz)
return uz
}
type zUnzipper struct {
zipper.Unzipper
}
func (uz zUnzipper) OpenReader(zipFile *os.File) (err error) {
var reader *zip.ReadCloser
if reader, err = zip.OpenReader(zipFile.Name()); err == nil {
uz.SetReader(reader)
}
return
}
func (uz zUnzipper) Decompress(targetFolderPathInFileSystem string) (err error) {
if zipReader, ok := uz.GetReader().(*zip.ReadCloser); ok {
if err = zipper.MakeSurePathExists(targetFolderPathInFileSystem+"/dummy.file", 0644); err == nil {
for _, entry := range zipReader.File {
outputPath := zipper.PrependPath(entry.Name, targetFolderPathInFileSystem)
if !entry.FileInfo().IsDir() {
if err = zipper.MakeSurePathExists(outputPath, 0644); err == nil {
var dstFile *os.File
if dstFile, err = os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, entry.Mode()); err == nil {
defer dstFile.Close()
var archivedFileData io.ReadCloser
if archivedFileData, err = entry.Open(); err == nil {
defer archivedFileData.Close()
_, err = io.Copy(dstFile, archivedFileData)
}
}
}
} else {
err = zipper.MakeSurePathExists(outputPath+"/dummy.file", 0644)
}
if err != nil {
break
}
}
}
}
return
}

92
zip/zip_test.go Normal file
View File

@ -0,0 +1,92 @@
package zip
import (
"io/fs"
"os"
"path/filepath"
"testing"
)
const (
content = "test-content"
)
func TestZipper(t *testing.T) {
archiveFolder := "testdata"
archiveFilename := archiveFolder + "/dummy"
testFileName := "testZipper.data"
if os.WriteFile(testFileName, []byte(content), 0644) == nil {
defer os.Remove(testFileName)
}
z := NewZipper(archiveFilename)
if z.GetWriter() != nil {
t.Log("Found unexpected writer")
t.Fail()
}
// Test adding files
if z.AddZipFiles([]string{testFileName}, "testfolder") != nil {
t.Log("Unable to add a single file")
t.Fail()
}
// Test adding more files than allowed
if z.AddFileToZip(testFileName, "testfolder/more") != nil {
t.Log("Unexpectedly failed in adding more than one file")
t.Fail()
}
z.Close()
uz := NewUnzipper(archiveFilename + ArchiveExt)
if uz.ExtractFiles("") != nil {
t.Log("Failed to extract files")
t.Fail()
} else {
if data, err := os.ReadFile("testfolder/" + testFileName); err != nil {
t.Log("Expected extracted file #1 is missing")
t.Fail()
} else if string(data) != content {
t.Logf("Unexpected extracted file #1 content: '%s'", string(data))
t.Fail()
}
if data, err := os.ReadFile("testfolder/more/" + testFileName); err != nil {
t.Log("Expected extracted file #2 is missing")
t.Fail()
} else if string(data) != content {
t.Logf("Unexpected extracted file #2 content: '%s'", string(data))
t.Fail()
}
}
// Clean up
removeRecursive("testfolder")
os.Remove(archiveFilename + ArchiveExt)
os.Remove(archiveFolder)
}
func removeRecursive(path string) {
filepath.Walk(path, deleteFiles)
filepath.WalkDir(path, deleteFolders)
filepath.WalkDir(path, deleteFolders)
filepath.WalkDir(path, deleteFolders)
}
func deleteFiles(path string, info fs.FileInfo, errIn error) (err error) {
if errIn != nil {
return
}
if !info.IsDir() {
err = os.Remove(path)
}
return err
}
func deleteFolders(path string, info fs.DirEntry, errIn error) (err error) {
if errIn != nil {
return
}
os.Remove(path)
return
}

9
zipper.code-workspace Normal file
View File

@ -0,0 +1,9 @@
{
"folders": [
{
"name": "git.sa-roci.de/oss/go_zipper",
"path": "."
}
],
"settings": {}
}

217
zipper_test.go Normal file
View File

@ -0,0 +1,217 @@
package zipper
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"testing"
)
const (
content = "test-content"
)
type dummyZipper struct {
Zipper
buffer map[string][]byte
multi bool
closed bool
}
func (dz dummyZipper) Flush() error {
return nil
}
func (dz *dummyZipper) Close() error {
if dz.closed {
return nil
}
dz.closed = true
return dz.Zipper.Close()
}
func (dz dummyZipper) Compress(fileToZip *os.File, pathInArchive string) (err error) {
if dz.closed {
return fmt.Errorf("compressor closed")
}
buf := bytes.Buffer{}
if _, err = io.Copy(&buf, fileToZip); err == nil {
dz.buffer[PrependPath(fileToZip.Name(), pathInArchive)] = buf.Bytes()
}
return
}
func (dz dummyZipper) OpenWriter(zipFile *os.File) (err error) {
dz.SetWriter(&dz)
return
}
func (dz dummyZipper) MultiFileSupported() (multi bool) {
return dz.multi
}
func newZipper(archiveFilename string, multi bool) dummyZipper {
dz := dummyZipper{
Zipper: NewBaseZipper(archiveFilename),
buffer: make(map[string][]byte),
multi: multi,
closed: false,
}
dz.SetCompressor(dz)
return dz
}
func TestZipper(t *testing.T) {
archiveFolder := "testdata"
archiveFilename := archiveFolder + "/dummy.arc"
testFileName := "testZipper.data"
if os.WriteFile(testFileName, []byte(content), 0644) == nil {
defer os.Remove(testFileName)
}
dz := newZipper(archiveFilename, false)
if dz.GetWriter() != nil {
t.Log("Found unexpected writer")
t.Fail()
}
// Test adding files
if dz.AddZipFiles([]string{testFileName}, "testfolder") != nil {
t.Log("Unable to add a single file")
t.Fail()
}
if fileContent, ok := dz.buffer["testfolder/"+testFileName]; ok {
if string(fileContent) != content {
t.Logf("Unexpected testfile content: '%s'", string(fileContent))
t.Fail()
}
} else {
t.Log("Failed to create appropriate subfolder in archive")
t.Fail()
}
// Test modification of compressor
if dz.SetCompressor(dz) == nil {
t.Log("Unexpectedly succeeded in changing the compressor interface")
t.Fail()
}
if dz.SetCompressor(nil) == nil {
t.Log("Unexpectedly succeeded in removing the compressor interface")
t.Fail()
}
// Test adding more files than allowed
if dz.AddFileToZip(testFileName, "badFolder") == nil {
t.Log("Unexpectedly succeeded in adding more than one file")
t.Fail()
}
// Clean up
dz.Close()
os.Remove(archiveFilename)
os.Remove(archiveFolder)
}
type dummyUnzipper struct {
Unzipper
buffer map[string][]byte
closed bool
}
func (duz *dummyUnzipper) Close() error {
if duz.closed {
return nil
}
duz.closed = true
return duz.Unzipper.Close()
}
func (duz *dummyUnzipper) Decompress(targetFolderPathInFileSystem string) (err error) {
if duz.buffer == nil {
return fmt.Errorf("nothing to decompress")
}
for path, data := range duz.buffer {
outputPath := PrependPath(path, targetFolderPathInFileSystem)
if err = MakeSurePathExists(outputPath, 0644); err == nil {
if err = os.WriteFile(outputPath, data, 0644); err != nil {
break
}
} else {
break
}
}
return err
}
func (duz *dummyUnzipper) OpenReader(zipFile *os.File) (err error) {
var binary []byte
if binary, err = io.ReadAll(zipFile); err == nil {
duz.SetReader(duz)
err = json.Unmarshal(binary, &duz.buffer)
}
return err
}
func newUnzipper(archiveFilename string) dummyUnzipper {
duz := dummyUnzipper{
Unzipper: NewBaseUnzipper(archiveFilename),
buffer: make(map[string][]byte),
}
duz.SetDecompressor(&duz)
return duz
}
func TestUnzipper(t *testing.T) {
archiveFolder := "testdata2"
archiveFilename := archiveFolder + "/dummy.arc"
data := make(map[string][]byte)
contentFolder := "extractedTest"
contentFilename := contentFolder + "/dummy.file"
data[contentFilename] = []byte(content)
if MakeSurePathExists(archiveFilename, 0644) == nil {
if archiveContent, err := json.Marshal(data); err == nil {
if os.WriteFile(archiveFilename, archiveContent, 0644) == nil {
defer func() {
os.Remove(archiveFilename)
os.Remove(archiveFolder)
}()
}
}
}
duz := newUnzipper(archiveFilename)
if duz.GetReader() != nil {
t.Log("Found unexpected reader")
t.Fail()
}
// Test modification of decompressor
if duz.SetDecompressor(&duz) == nil {
t.Log("Unexpectedly succeeded in changing the decompressor interface")
t.Fail()
}
if duz.SetDecompressor(nil) == nil {
t.Log("Unexpectedly succeeded in removing the decompressor interface")
t.Fail()
}
// Test decompression
if duz.ExtractFiles("") != nil {
t.Log("Decompression failed")
t.Fail()
} else {
if data, err := os.ReadFile(contentFilename); err != nil {
t.Log("Expected extracted file is missing")
t.Fail()
} else if string(data) != content {
t.Logf("Unexpected extracted file content: '%s'", string(data))
t.Fail()
}
}
// Clean up
duz.Close()
os.Remove(contentFilename)
os.Remove(contentFolder)
}