buildr/internal/archive/tree.go
Peter 1261932bdc
All checks were successful
continuous-integration/drone/push Build is passing
refactor: apply golangci-lint findings
2023-06-22 19:16:00 +02:00

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()
}