package cli import ( "fmt" "path/filepath" "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{ IgnoreDotfiles: ctx.Bool("IgnoreDotfiles"), 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 if showProgress { enumProgress = make(chan scanner.EnumerateStatus, 1) go func() { 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 } } log.Debugf("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 outFile.Close() // Phase 2: Scan - read file contents and generate manifest var scanProgress chan scanner.ScanStatus if showProgress { scanProgress = make(chan scanner.ScanStatus, 1) go func() { 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) 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 }