192 lines
3.8 KiB
Go
192 lines
3.8 KiB
Go
package archive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.icb4dc0.de/buildr/buildr/internal/ioutils"
|
|
)
|
|
|
|
var defaultCreationTime = time.Unix(0, 0)
|
|
|
|
type fileToTar struct {
|
|
sourcePath string
|
|
targetFileName string
|
|
}
|
|
|
|
type filesToTar []fileToTar
|
|
|
|
func (files *filesToTar) IsEmpty() bool {
|
|
return len(*files) == 0
|
|
}
|
|
|
|
func (files *filesToTar) Add(toAdd fileToTar) {
|
|
ref := *files
|
|
ref = append(ref, toAdd)
|
|
*files = ref
|
|
}
|
|
|
|
func (files *filesToTar) Remove(toRemove string) bool {
|
|
ref := *files
|
|
|
|
for i := range ref {
|
|
if ref[i].targetFileName == toRemove {
|
|
ref = append(ref[:i], ref[i+1:]...)
|
|
*files = ref
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func newNode() *archiveNode {
|
|
return &archiveNode{
|
|
children: make(map[string]*archiveNode),
|
|
files: new(filesToTar),
|
|
}
|
|
}
|
|
|
|
type archiveNode struct {
|
|
children map[string]*archiveNode
|
|
files *filesToTar
|
|
}
|
|
|
|
func (n *archiveNode) writeToTar(writer *tar.Writer, fileSystem fs.StatFS, parent string) error {
|
|
for segment, child := range n.children {
|
|
if child.isEmpty() {
|
|
continue
|
|
}
|
|
segmentPath := filepath.Join(parent, segment)
|
|
header := &tar.Header{
|
|
Name: segmentPath,
|
|
Typeflag: tar.TypeDir,
|
|
// Use a fixed Mode, so that this isn't sensitive to the directory and umask
|
|
// under which it was created. Additionally, windows can only set 0222,
|
|
// 0444, or 0666, none of which are executable.
|
|
Mode: 0o555,
|
|
ModTime: defaultCreationTime,
|
|
}
|
|
|
|
if err := writer.WriteHeader(header); err != nil {
|
|
return fmt.Errorf("failed to write tar header: %w", err)
|
|
}
|
|
|
|
if err := child.writeToTar(writer, fileSystem, segmentPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, fileSpec := range *n.files {
|
|
info, err := fileSystem.Stat(fileSpec.sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := fileSystem.Open(fileSpec.sourcePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
header, err := tar.FileInfoHeader(info, path.Join(parent, fileSpec.targetFileName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
header.Name = path.Join(parent, fileSpec.targetFileName)
|
|
|
|
if err = writer.WriteHeader(header); err != nil {
|
|
return errors.Join(err, f.Close())
|
|
}
|
|
|
|
if _, err = ioutils.CopyWithPooledBuffer(writer, f); err != nil {
|
|
return errors.Join(err, f.Close())
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (n *archiveNode) remove(pathToRemove string) bool {
|
|
if pathToRemove == "" {
|
|
return false
|
|
}
|
|
|
|
split := strings.Split(pathToRemove, "/")
|
|
|
|
last := split[len(split)-1]
|
|
|
|
current := n
|
|
for _, segment := range split[:len(split)-1] {
|
|
if child, ok := current.children[segment]; ok {
|
|
current = child
|
|
continue
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
if _, ok := current.children[last]; ok {
|
|
delete(current.children, last)
|
|
return true
|
|
} else {
|
|
return current.files.Remove(last)
|
|
}
|
|
}
|
|
|
|
func (n *archiveNode) addFile(sourcePath, targetPath string) {
|
|
dirPath, fileName := filepath.Split(targetPath)
|
|
current := n
|
|
for _, segment := range strings.Split(dirPath, "/") {
|
|
if segment == "" {
|
|
continue
|
|
}
|
|
if child, ok := current.children[segment]; ok {
|
|
current = child
|
|
} else {
|
|
newNode := &archiveNode{
|
|
children: make(map[string]*archiveNode),
|
|
files: new(filesToTar),
|
|
}
|
|
current.children[segment] = newNode
|
|
current = newNode
|
|
}
|
|
}
|
|
|
|
current.files.Add(fileToTar{
|
|
sourcePath: sourcePath,
|
|
targetFileName: fileName,
|
|
})
|
|
}
|
|
|
|
func (n *archiveNode) addDir(dirPath string) {
|
|
current := n
|
|
for _, segment := range strings.Split(dirPath, "/") {
|
|
if segment == "" {
|
|
continue
|
|
}
|
|
|
|
if child, ok := current.children[segment]; ok {
|
|
current = child
|
|
} else {
|
|
newNode := newNode()
|
|
current.children[segment] = newNode
|
|
current = newNode
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *archiveNode) isEmpty() bool {
|
|
return len(n.children) == 0 && n.files.IsEmpty()
|
|
}
|