154 lines
3.5 KiB
Go
154 lines
3.5 KiB
Go
package archiver
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Create creates a tar.gz archive from the given files relative to rootDir
|
|
// Returns: archive reader, archive size, error
|
|
func Create(files []string, rootDir string) (io.Reader, int64, error) {
|
|
var buf bytes.Buffer
|
|
gzWriter := gzip.NewWriter(&buf)
|
|
tarWriter := tar.NewWriter(gzWriter)
|
|
|
|
for _, file := range files {
|
|
// Get full path
|
|
fullPath := filepath.Join(rootDir, file)
|
|
|
|
// Open file
|
|
f, err := os.Open(fullPath)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("failed to open file %s: %w", file, err)
|
|
}
|
|
|
|
// Get file info
|
|
info, err := f.Stat()
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, 0, fmt.Errorf("failed to stat file %s: %w", file, err)
|
|
}
|
|
|
|
// Create tar header
|
|
header := &tar.Header{
|
|
Name: file, // Use relative path
|
|
Size: info.Size(),
|
|
Mode: int64(info.Mode()),
|
|
ModTime: info.ModTime(),
|
|
}
|
|
|
|
// Write header
|
|
if err := tarWriter.WriteHeader(header); err != nil {
|
|
f.Close()
|
|
return nil, 0, fmt.Errorf("failed to write tar header for %s: %w", file, err)
|
|
}
|
|
|
|
// Write file content
|
|
if _, err := io.Copy(tarWriter, f); err != nil {
|
|
f.Close()
|
|
return nil, 0, fmt.Errorf("failed to write file content for %s: %w", file, err)
|
|
}
|
|
|
|
f.Close()
|
|
}
|
|
|
|
// Close tar writer
|
|
if err := tarWriter.Close(); err != nil {
|
|
return nil, 0, fmt.Errorf("failed to close tar writer: %w", err)
|
|
}
|
|
|
|
// Close gzip writer
|
|
if err := gzWriter.Close(); err != nil {
|
|
return nil, 0, fmt.Errorf("failed to close gzip writer: %w", err)
|
|
}
|
|
|
|
size := int64(buf.Len())
|
|
return &buf, size, nil
|
|
}
|
|
|
|
// Extract extracts a tar.gz archive to the destination directory
|
|
// Returns: list of extracted files, error
|
|
func Extract(archive io.Reader, destDir string) ([]string, error) {
|
|
// Create gzip reader
|
|
gzReader, err := gzip.NewReader(archive)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
|
|
}
|
|
defer gzReader.Close()
|
|
|
|
// Create tar reader
|
|
tarReader := tar.NewReader(gzReader)
|
|
|
|
var extractedFiles []string
|
|
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read tar header: %w", err)
|
|
}
|
|
|
|
// Get target path
|
|
targetPath := filepath.Join(destDir, header.Name)
|
|
|
|
// Ensure directory exists
|
|
dir := filepath.Dir(targetPath)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
|
|
// Create file
|
|
f, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create file %s: %w", targetPath, err)
|
|
}
|
|
|
|
// Copy content
|
|
if _, err := io.Copy(f, tarReader); err != nil {
|
|
f.Close()
|
|
return nil, fmt.Errorf("failed to write file content for %s: %w", targetPath, err)
|
|
}
|
|
|
|
f.Close()
|
|
extractedFiles = append(extractedFiles, header.Name)
|
|
}
|
|
|
|
return extractedFiles, nil
|
|
}
|
|
|
|
// List lists files in the archive without extracting
|
|
func List(archive io.Reader) ([]string, error) {
|
|
// Create gzip reader
|
|
gzReader, err := gzip.NewReader(archive)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
|
|
}
|
|
defer gzReader.Close()
|
|
|
|
// Create tar reader
|
|
tarReader := tar.NewReader(gzReader)
|
|
|
|
var files []string
|
|
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read tar header: %w", err)
|
|
}
|
|
|
|
files = append(files, header.Name)
|
|
}
|
|
|
|
return files, nil
|
|
}
|