This file is indexed.

/usr/bin/sup-tweak-labels is in sup-mail 0.22.1-2.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/usr/bin/ruby

$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])

require 'trollop'
require "sup"

class Float
  def to_s; sprintf '%.2f', self; end
   def to_time_s
     infinite? ? "unknown" : super
   end
end

class Numeric
  def to_time_s
    i = to_i
    sprintf "%d:%02d:%02d", i / 3600, (i / 60) % 60, i % 60
  end
end

def time
  startt = Time.now
  yield
  Time.now - startt
end

opts = Trollop::options do
  version "sup-tweak-labels (sup #{Redwood::VERSION})"
  banner <<EOS
Batch modification of message state for messages already in the index.

Usage:
  sup-tweak-labels [options] <source>*

where <source>* is zero or more source URIs. Supported source URI schemes can
be seen by running "sup-add --help".

Options:
EOS
  opt :add, "One or more labels (comma-separated) to add to every message from the specified sources", :default => ""
  opt :remove, "One or more labels (comma-separated) to remove from every message from the specified sources, if those labels are present", :default => ""
  opt :query, "A Sup search query", :type => String

  text <<EOS

Other options:
EOS
  opt :verbose, "Print message ids as they're processed."
  opt :very_verbose, "Print message names and subjects as they're processed."
  opt :all_sources, "Scan over all sources.", :short => :none
  opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
  opt :no_sync_back, "Do not sync back to the original Maildir."
  opt :version, "Show version information", :short => :none
end
opts[:verbose] = true if opts[:very_verbose]

add_labels = opts[:add].to_set_of_symbols ","
remove_labels = opts[:remove].to_set_of_symbols ","

Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?

Redwood::start
index = Redwood::Index.init
index.lock_interactively or exit

begin
  index.load

  source_ids = if opts[:all_sources]
    Redwood::SourceManager.sources
  else
    ARGV.map do |uri|
      Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
    end
  end.map { |s| s.id }
  Trollop::die "nothing to do: no sources" if source_ids.empty?

  query = "(" + source_ids.map { |id| "source_id:#{id}" }.join(" OR ") + ")"
  if add_labels.empty?
    ## if all we're doing is removing labels, we can further restrict the
    ## query to only messages with those labels
    query += " (" + remove_labels.map { |l| "label:#{l}" }.join(" OR ") + ")"
  end
  query += ' AND ' + opts[:query] if opts[:query]

  parsed_query = index.parse_query query
  parsed_query.merge! :load_spam => true, :load_deleted => true, :load_killed => true
  ids = index.to_enum(:each_id, parsed_query)
  num_total = index.num_results_for parsed_query

  $stderr.puts "Found #{num_total} documents across #{source_ids.length} sources. Scanning..."

  num_changed = num_scanned = 0
  last_info_time = start_time = Time.now
  ids.each do |id|
    num_scanned += 1

    m = index.build_message id
    old_labels = m.labels.dup

    m.labels += add_labels
    m.labels -= remove_labels

    unless m.labels == old_labels
      num_changed += 1
      puts "From #{m.from}, subject: #{m.subj}" if opts[:very_verbose]
      puts "#{m.id}: {#{old_labels.to_a.join ','}} => {#{m.labels.to_a.join ','}}" if opts[:verbose]
      puts if opts[:very_verbose]
      unless opts[:dry_run]
        index.update_message_state [m, false]
        m.sync_back unless opts[:no_sync_back]
      end
    end

    if Time.now - last_info_time > 60
      last_info_time = Time.now
      elapsed = last_info_time - start_time
      pctdone = 100.0 * num_scanned.to_f / num_total.to_f
      remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
      $stderr.puts "## #{num_scanned} (#{pctdone}%) read; #{elapsed.to_time_s} elapsed; #{remaining.to_time_s} remaining"
    end
  end
  $stderr.puts "Scanned #{num_scanned} / #{num_total} messages and changed #{num_changed}."

  unless num_changed == 0
    $stderr.puts "Optimizing index..."
    index.optimize unless opts[:dry_run]
  end

rescue Exception => e
  File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
  raise
ensure
  index.save
  Redwood::finish
  index.unlock
end