diff --git a/mfer/deserialize.go b/mfer/deserialize.go index 66558e1..6a0d19c 100644 --- a/mfer/deserialize.go +++ b/mfer/deserialize.go @@ -61,7 +61,7 @@ func validateMagic(dat []byte) bool { // NewManifestFromReader reads a manifest from an io.Reader. func NewManifestFromReader(input io.Reader) (*manifest, error) { - m := New() + m := &manifest{} dat, err := io.ReadAll(input) if err != nil { return nil, err @@ -102,8 +102,3 @@ func NewManifestFromFile(fs afero.Fs, path string) (*manifest, error) { defer func() { _ = f.Close() }() return NewManifestFromReader(f) } - -// NewFromProto is deprecated, use NewManifestFromReader instead. -func NewFromProto(input io.Reader) (*manifest, error) { - return NewManifestFromReader(input) -} diff --git a/mfer/manifest.go b/mfer/manifest.go index 6c35956..00f4b9e 100644 --- a/mfer/manifest.go +++ b/mfer/manifest.go @@ -2,139 +2,24 @@ package mfer import ( "bytes" - "context" - "errors" "fmt" - "io/fs" - "os" - "path" - "path/filepath" - "strings" - - "github.com/spf13/afero" - "sneak.berlin/go/mfer/internal/log" ) -type manifestFile struct { - path string - info fs.FileInfo -} - -func (m *manifestFile) String() string { - return fmt.Sprintf("", m.path) -} - +// manifest holds the internal representation of a manifest file. +// Use NewManifestFromFile or NewManifestFromReader to load an existing manifest, +// or use Builder to create a new one. type manifest struct { - sourceFS []afero.Fs - files []*manifestFile - scanOptions *ManifestScanOptions - totalFileSize int64 - pbInner *MFFile - pbOuter *MFFileOuter - output *bytes.Buffer - ctx context.Context - errors []*error + pbInner *MFFile + pbOuter *MFFileOuter + output *bytes.Buffer } func (m *manifest) String() string { - return fmt.Sprintf("", len(m.files), m.totalFileSize) -} - -// ManifestScanOptions configures behavior when scanning directories for manifest generation. -type ManifestScanOptions struct { - IncludeDotfiles bool // Include files and directories starting with a dot (default: exclude) - FollowSymLinks bool // Resolve symlinks instead of skipping them -} - -func (m *manifest) HasError() bool { - return len(m.errors) > 0 -} - -func (m *manifest) AddError(e error) *manifest { - m.errors = append(m.errors, &e) - return m -} - -func (m *manifest) WithContext(c context.Context) *manifest { - m.ctx = c - return m -} - -func (m *manifest) addInputPath(inputPath string) error { - abs, err := filepath.Abs(inputPath) - if err != nil { - return err - } - // Validate path exists - if _, err := os.Stat(abs); err != nil { - return fmt.Errorf("path does not exist: %s", inputPath) - } - afs := afero.NewReadOnlyFs(afero.NewBasePathFs(afero.NewOsFs(), abs)) - return m.addInputFS(afs) -} - -func (m *manifest) addInputFS(f afero.Fs) error { - if f == nil { - return errors.New("filesystem cannot be nil") - } - // Validate filesystem is accessible by statting root - if _, err := f.Stat("/"); err != nil { - return fmt.Errorf("filesystem not accessible: %w", err) - } - if m.sourceFS == nil { - m.sourceFS = make([]afero.Fs, 0) - } - m.sourceFS = append(m.sourceFS, f) - return nil -} - -// New creates an empty manifest. -func New() *manifest { - m := &manifest{} - return m -} - -// NewFromPaths creates a manifest configured to scan the given filesystem paths. -func NewFromPaths(options *ManifestScanOptions, inputPaths ...string) (*manifest, error) { - log.Dump(inputPaths) - m := New() - m.scanOptions = options - for _, p := range inputPaths { - err := m.addInputPath(p) - if err != nil { - return nil, err - } - } - return m, nil -} - -// NewFromFS creates a manifest configured to scan the given afero filesystem. -func NewFromFS(options *ManifestScanOptions, fs afero.Fs) (*manifest, error) { - m := New() - m.scanOptions = options - err := m.addInputFS(fs) - if err != nil { - return nil, err - } - return m, nil -} - -func (m *manifest) GetFileCount() int64 { + count := 0 if m.pbInner != nil { - return int64(len(m.pbInner.Files)) + count = len(m.pbInner.Files) } - return int64(len(m.files)) -} - -func (m *manifest) GetTotalFileSize() int64 { - if m.pbInner != nil { - var total int64 - for _, f := range m.pbInner.Files { - total += f.Size - } - return total - } - return m.totalFileSize + return fmt.Sprintf("", count) } // Files returns all file entries from a loaded manifest. @@ -144,72 +29,3 @@ func (m *manifest) Files() []*MFFilePath { } return m.pbInner.Files } - -func pathIsHidden(p string) bool { - tp := path.Clean(p) - if strings.HasPrefix(tp, ".") { - return true - } - for { - d, f := path.Split(tp) - if strings.HasPrefix(f, ".") { - return true - } - if d == "" { - return false - } - tp = d[0 : len(d)-1] // trim trailing slash from dir - } -} - -func (m *manifest) addFile(p string, fi fs.FileInfo, sfsIndex int) error { - if !m.scanOptions.IncludeDotfiles && pathIsHidden(p) { - return nil - } - if fi == nil { - // fi should come from Walk; if nil, stat to get info - var err error - fi, err = m.sourceFS[sfsIndex].Stat(p) - if err != nil { - return err - } - } - if fi.IsDir() { - // manifests contain only files, directories are implied. - return nil - } - cleanPath := p - if cleanPath[0:1] == "/" { - cleanPath = cleanPath[1:] - } - nf := &manifestFile{ - path: cleanPath, - info: fi, - } - m.files = append(m.files, nf) - m.totalFileSize = m.totalFileSize + fi.Size() - return nil -} - -func (m *manifest) Scan() error { - for idx, sfs := range m.sourceFS { - if sfs == nil { - return errors.New("invalid source fs") - } - e := afero.Walk(sfs, "/", func(p string, info fs.FileInfo, err error) error { - // Check for context cancellation - if m.ctx != nil { - select { - case <-m.ctx.Done(): - return m.ctx.Err() - default: - } - } - return m.addFile(p, info, idx) - }) - if e != nil { - return e - } - } - return nil -} diff --git a/mfer/mfer_test.go b/mfer/mfer_test.go deleted file mode 100644 index b0c3981..0000000 --- a/mfer/mfer_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package mfer - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPathHiddenFunc(t *testing.T) { - assert.False(t, pathIsHidden("/a/b/c/hello.txt")) - assert.True(t, pathIsHidden("/a/b/c/.hello.txt")) - assert.True(t, pathIsHidden("/a/.b/c/hello.txt")) - assert.True(t, pathIsHidden("/.a/b/c/hello.txt")) - assert.False(t, pathIsHidden("./a/b/c/hello.txt")) -} diff --git a/mfer/output.go b/mfer/output.go deleted file mode 100644 index 0ff0e7d..0000000 --- a/mfer/output.go +++ /dev/null @@ -1,34 +0,0 @@ -package mfer - -import ( - "io" - "os" -) - -// WriteToFile writes the manifest to the given path. -// It will overwrite any existing file. Callers should check for existing files -// before calling if overwrite confirmation is needed. -func (m *manifest) WriteToFile(path string) error { - f, err := os.Create(path) - if err != nil { - return err - } - defer func() { _ = f.Close() }() - - return m.Write(f) -} - -func (m *manifest) Write(output io.Writer) error { - if m.pbOuter == nil { - err := m.generate() - if err != nil { - return err - } - } - - _, err := output.Write(m.output.Bytes()) - if err != nil { - return err - } - return nil -}