go-mtree/tar.go

186 lines
4.5 KiB
Go
Raw Normal View History

package mtree
import (
"archive/tar"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
// Streamer interface that wraps an io.ReadCloser with a function that will
// return it's Hierarchy
type Streamer interface {
io.ReadCloser
Hierarchy() (*DirectoryHierarchy, error)
}
// NewTarStreamer streams a tar archive and creates a file hierarchy based off
// of the tar metadata headers
func NewTarStreamer(r io.Reader, keywords []string) Streamer {
pR, pW := io.Pipe()
ts := &tarStream{
pipeReader: pR,
pipeWriter: pW,
creator: dhCreator{DH: &DirectoryHierarchy{}},
teeReader: io.TeeReader(r, pW),
tarReader: tar.NewReader(pR),
keywords: keywords,
}
go ts.readHeaders() // I don't like this
return ts
}
type tarStream struct {
creator dhCreator
pipeReader *io.PipeReader
pipeWriter *io.PipeWriter
teeReader io.Reader
tarReader *tar.Reader
keywords []string
err error
}
func (ts *tarStream) readHeaders() {
// We have to start with the directory we're in, and anything beyond these
// items is determined at the time a tar is extracted.
e := Entry{
Name: ".",
Keywords: []string{"size=0", "type=dir"},
}
ts.creator.curDir = &e
ts.creator.DH.Entries = append(ts.creator.DH.Entries, e)
for {
hdr, err := ts.tarReader.Next()
if err != nil {
ts.pipeReader.CloseWithError(err)
return
}
// Because the content of the file may need to be read by several
// KeywordFuncs, it needs to be an io.Seeker as well. So, just reading from
// ts.tarReader is not enough.
tmpFile, err := ioutil.TempFile("", "ts.payload.")
if err != nil {
ts.pipeReader.CloseWithError(err)
return
}
// for good measure
if err := tmpFile.Chmod(0600); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
if _, err := io.Copy(tmpFile, ts.tarReader); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
// Alright, it's either file or directory
e := Entry{
Name: filepath.Base(hdr.Name),
Pos: len(ts.creator.DH.Entries),
Type: RelativeType,
}
// now collect keywords on the file
for _, keyword := range ts.keywords {
if keyFunc, ok := KeywordFuncs[keyword]; ok {
val, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
if err != nil {
ts.setErr(err)
}
e.Keywords = append(e.Keywords, val)
// don't forget to reset the reader
if _, err := tmpFile.Seek(0, 0); err != nil {
tmpFile.Close()
os.Remove(tmpFile.Name())
ts.pipeReader.CloseWithError(err)
return
}
}
}
tmpFile.Close()
os.Remove(tmpFile.Name())
// compare directories, to determine parent of the current entry
cd := compareDir(filepath.Dir(hdr.Name), ts.creator.curDir.Path())
switch {
case cd == sameDir:
e.Parent = ts.creator.curDir
if e.Parent != nil {
e.Parent.Children = append(e.Parent.Children, &e)
}
case cd == parentDir:
e.Parent = ts.creator.curDir.Parent
if e.Parent != nil {
e.Parent.Children = append(e.Parent.Children, &e)
}
}
if hdr.FileInfo().IsDir() {
ts.creator.curDir = &e
}
// TODO getting the parent child relationship of these entries!
if hdr.FileInfo().IsDir() {
log.Println(strings.Split(hdr.Name, "/"), strings.Split(ts.creator.curDir.Path(), "/"))
}
ts.creator.DH.Entries = append(ts.creator.DH.Entries, e)
// Now is the wacky part of building out the entries. Since we can not
// control how the archive was assembled, can only take in the order given.
// Using `/set` will be tough. Hopefully i can do the directory stepping
// with relative paths, but even then I may get a new directory, and not
// the files first, but its directories first. :-\
}
}
type relationship int
const (
unknownDir relationship = iota
sameDir
childDir
parentDir
)
func compareDir(curDir, prevDir string) relationship {
curDir = filepath.Clean(curDir)
prevDir = filepath.Clean(prevDir)
if curDir == prevDir {
return sameDir
}
if filepath.Dir(curDir) == prevDir {
return childDir
}
if curDir == filepath.Dir(prevDir) {
return parentDir
}
return unknownDir
}
func (ts *tarStream) setErr(err error) {
ts.err = err
}
func (ts *tarStream) Read(p []byte) (n int, err error) {
return ts.teeReader.Read(p)
}
func (ts *tarStream) Close() error {
return ts.pipeReader.Close()
}
func (ts *tarStream) Hierarchy() (*DirectoryHierarchy, error) {
if ts.err != nil && ts.err != io.EOF {
return nil, ts.err
}
return ts.creator.DH, nil
}