Class: Metaclean::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/metaclean/runner.rb

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Runner

Returns a new instance of Runner.



18
19
20
21
22
23
24
# File 'lib/metaclean/runner.rb', line 18

def initialize(options)
  @options = options
  # Paths that couldn't be read during discovery (missing arg, unreadable
  # directory). Tracked so a partially-scanned batch exits non-zero instead
  # of letting automation mistake "some files cleaned" for "everything done".
  @scan_errors = 0
end

Instance Method Details

#clean_paths(paths) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/metaclean/runner.rb', line 53

def clean_paths(paths)
  files = expand_files(paths)
  # See inspect_paths: nothing to act on is a non-zero condition, not success.
  if files.empty?
    Display.warning('No files to process.')
    exit 1
  end

  announce_tools

  # Confirmation prompt — skipped for --force and --dry-run (since
  # dry-run never modifies anything anyway).
  unless @options[:force] || @options[:dry_run]
    action = @options[:in_place] ? 'OVERWRITE' : 'create cleaned copies of'
    puts Display.c("About to #{action} #{files.size} file(s).", :yellow)
    if @options[:in_place]
      # The .bak IS the original — metadata intact. Say so plainly: a user who
      # shares the "cleaned" folder would otherwise leak it via the backup.
      puts Display.c('Each <file>.bak is the ORIGINAL, with all metadata still in it — ' \
                     'delete or move the .bak files before sharing the folder.', :yellow)
    end
    print Display.c('Proceed? [y/N] ', :bold)
    ans = $stdin.gets&.strip&.downcase # gets → nil on Ctrl-D
    # Abort (no/blank/EOF) is a non-zero exit, not silent success — a
    # non-interactive caller must not read "Aborted." as "files were cleaned".
    unless %w[y yes].include?(ans)
      Display.warning('Aborted.')
      exit 1
    end
  end

  summary = { cleaned: 0, unverified: 0, failed: 0, removed_total: 0, residual_files: 0 }

  # index/total let clean_one render "[3/47]" in batch mode.
  files.each_with_index do |file, idx|
    result = clean_one(file, index: idx + 1, total: files.size)
    summary[result[:status]] += 1
    summary[:removed_total]  += result[:removed].to_i
    summary[:residual_files] += 1 if result[:residual].to_i.positive?
  rescue Error, SystemCallError => e
    # One bad file shouldn't abort the whole batch. SystemCallError
    # (Errno::*: disk full, permission denied, read-only fs) is a SIBLING
    # of our Error, not a subclass, so it must be named explicitly or it
    # would escape this rescue and crash the run with a raw backtrace.
    warn Display.error("#{file}: #{e.message}")
    summary[:failed] += 1
  end

  print_summary(summary)

  # Non-zero exit so CI/scripts can detect a failed or not-fully-verified file,
  # OR a batch that was never fully discovered (a path/dir we couldn't read).
  exit 1 if summary[:failed].positive? || summary[:unverified].positive? || @scan_errors.positive?
end

#inspect_paths(paths) ⇒ Object

Public entry points: one for ‘–inspect`, one for the cleaning flow.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/metaclean/runner.rb', line 28

def inspect_paths(paths)
  files = expand_files(paths)
  if files.empty?
    Display.warning('No files to inspect.')
    exit 1
  end
  failed = 0
  files.each do |file|
    Display.header "📄 #{file}"
    meta = Exiftool.read(file)
    Display.section "Metadata (#{Display.count_embedded(meta)} embedded tags)"
    Display.(meta)
  rescue Error, SystemCallError => e
    # One unreadable/odd file shouldn't abort inspecting the rest — mirrors
    # the per-file rescue in the clean batch.
    warn Display.error("#{file}: #{e.message}")
    failed += 1
  end

  # A file we couldn't read is a non-zero condition: a script using --inspect
  # as a gate must not mistake "couldn't read it" for "no metadata". Discovery
  # errors (missing paths / unreadable dirs) count too.
  exit 1 if failed.positive? || @scan_errors.positive?
end