Changed the default behavior to exclude dotfiles (files/dirs starting with .) which is the more common use case. Added --include-dotfiles flag for when hidden files need to be included in the manifest.
126 lines
3.1 KiB
Go
126 lines
3.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/spf13/afero"
|
|
"github.com/urfave/cli/v2"
|
|
"sneak.berlin/go/mfer/internal/log"
|
|
"sneak.berlin/go/mfer/internal/scanner"
|
|
)
|
|
|
|
func (mfa *CLIApp) generateManifestOperation(ctx *cli.Context) error {
|
|
log.Debug("generateManifestOperation()")
|
|
|
|
opts := &scanner.Options{
|
|
IncludeDotfiles: ctx.Bool("IncludeDotfiles"),
|
|
FollowSymLinks: ctx.Bool("FollowSymLinks"),
|
|
Fs: mfa.Fs,
|
|
}
|
|
|
|
s := scanner.NewWithOptions(opts)
|
|
|
|
// Phase 1: Enumeration - collect paths and stat files
|
|
args := ctx.Args()
|
|
showProgress := ctx.Bool("progress")
|
|
|
|
// Set up enumeration progress reporting
|
|
var enumProgress chan scanner.EnumerateStatus
|
|
var enumWg sync.WaitGroup
|
|
if showProgress {
|
|
enumProgress = make(chan scanner.EnumerateStatus, 1)
|
|
enumWg.Add(1)
|
|
go func() {
|
|
defer enumWg.Done()
|
|
for status := range enumProgress {
|
|
log.Progressf("Enumerating: %d files, %.1f MB",
|
|
status.FilesFound,
|
|
float64(status.BytesFound)/1e6)
|
|
}
|
|
log.ProgressDone()
|
|
}()
|
|
}
|
|
|
|
if args.Len() == 0 {
|
|
// Default to current directory
|
|
if err := s.EnumeratePath(".", enumProgress); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Collect all paths first
|
|
paths := make([]string, 0, args.Len())
|
|
for i := 0; i < args.Len(); i++ {
|
|
ap, err := filepath.Abs(args.Get(i))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("enumerating path: %s", ap)
|
|
paths = append(paths, ap)
|
|
}
|
|
if err := s.EnumeratePaths(enumProgress, paths...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
enumWg.Wait()
|
|
|
|
log.Infof("enumerated %d files, %d bytes total", s.FileCount(), s.TotalBytes())
|
|
|
|
// Check if output file exists
|
|
outputPath := ctx.String("output")
|
|
if exists, _ := afero.Exists(mfa.Fs, outputPath); exists {
|
|
if !ctx.Bool("force") {
|
|
return fmt.Errorf("output file %s already exists (use --force to overwrite)", outputPath)
|
|
}
|
|
}
|
|
|
|
// Open output file
|
|
outFile, err := mfa.Fs.Create(outputPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create output file: %w", err)
|
|
}
|
|
defer func() { _ = outFile.Close() }()
|
|
|
|
// Phase 2: Scan - read file contents and generate manifest
|
|
var scanProgress chan scanner.ScanStatus
|
|
var scanWg sync.WaitGroup
|
|
if showProgress {
|
|
scanProgress = make(chan scanner.ScanStatus, 1)
|
|
scanWg.Add(1)
|
|
go func() {
|
|
defer scanWg.Done()
|
|
for status := range scanProgress {
|
|
if status.ETA > 0 {
|
|
log.Progressf("Scanning: %d/%d files, %.1f MB/s, ETA %s",
|
|
status.ScannedFiles,
|
|
status.TotalFiles,
|
|
status.BytesPerSec/1e6,
|
|
status.ETA.Round(time.Second))
|
|
} else {
|
|
log.Progressf("Scanning: %d/%d files, %.1f MB/s",
|
|
status.ScannedFiles,
|
|
status.TotalFiles,
|
|
status.BytesPerSec/1e6)
|
|
}
|
|
}
|
|
log.ProgressDone()
|
|
}()
|
|
}
|
|
|
|
err = s.ToManifest(ctx.Context, outFile, scanProgress)
|
|
scanWg.Wait()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate manifest: %w", err)
|
|
}
|
|
|
|
if !ctx.Bool("quiet") {
|
|
elapsed := time.Since(mfa.startupTime).Seconds()
|
|
rate := float64(s.TotalBytes()) / elapsed / 1e6
|
|
log.Infof("wrote %d files (%.1f MB) to %s in %.1fs (%.1f MB/s)", s.FileCount(), float64(s.TotalBytes())/1e6, outputPath, elapsed, rate)
|
|
}
|
|
|
|
return nil
|
|
}
|