8 def to_s; sprintf '%.2f', self; end
10 infinite? ? "unknown" : super
17 sprintf "%d:%02d:%02d", i / 3600, (i / 60) % 60, i % 60
27 opts = Trollop::options do
28 version "sup-tweak-labels (sup #{Redwood::VERSION})"
30 Batch modification of message state for messages already in the index.
33 sup-tweak-labels [options] <source>*
35 where <source>* is zero or more source URIs. Supported source URI schemes can
36 be seen by running "sup-add --help".
40 opt :add, "One or more labels (comma-separated) to add to every message from the specified sources", :type => String
41 opt :remove, "One or more labels (comma-separated) to remove from every message from the specified sources, if those labels are present", :type => String
42 opt :query, "A Sup search query", :type => String
48 opt :verbose, "Print message ids as they're processed."
49 opt :very_verbose, "Print message names and subjects as they're processed."
50 opt :all_sources, "Scan over all sources.", :short => :none
51 opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
52 opt :version, "Show version information", :short => :none
54 opts[:verbose] = true if opts[:very_verbose]
56 add_labels = (opts[:add] || "").split(",").map { |l| l.intern }.uniq
57 remove_labels = (opts[:remove] || "").split(",").map { |l| l.intern }.uniq
59 Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
63 index = Redwood::Index.new
71 index.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
74 Trollop::die "nothing to do: no sources" if source_ids.empty?
76 query = "+(" + source_ids.map { |id| "source_id:#{id}" }.join(" OR ") + ")"
78 ## if all we're doing is removing labels, we can further restrict the
79 ## query to only messages with those labels
80 query += " +(" + remove_labels.map { |l| "label:#{l}" }.join(" ") + ")"
82 query += ' ' + opts[:query] if opts[:query]
84 qobj, opts = Redwood::Index.parse_user_query_string query
85 query = Redwood::Index.build_query opts.merge(:qobj => qobj)
87 results = index.ferret.search query, :limit => :all
88 num_total = results.total_hits
90 $stderr.puts "Found #{num_total} documents across #{source_ids.length} sources. Scanning..."
92 num_changed = num_scanned = 0
93 last_info_time = start_time = Time.now
94 results.hits.each do |hit|
98 m = index.build_message id
99 old_labels = m.labels.clone
101 m.labels += add_labels
102 m.labels -= remove_labels
103 m.labels = m.labels.uniq
105 unless m.labels.sort_by { |s| s.to_s } == old_labels.sort_by { |s| s.to_s }
107 puts "From #{m.from}, subject: #{m.subj}" if opts[:very_verbose]
108 puts "#{m.id}: {#{old_labels.join ','}} => {#{m.labels.join ','}}" if opts[:verbose]
109 puts if opts[:very_verbose]
110 index.sync_message m unless opts[:dry_run]
113 if Time.now - last_info_time > 60
114 last_info_time = Time.now
115 elapsed = last_info_time - start_time
116 pctdone = 100.0 * num_scanned.to_f / num_total.to_f
117 remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
118 $stderr.puts "## #{num_scanned} (#{pctdone}%) read; #{elapsed.to_time_s} elapsed; #{remaining.to_time_s} remaining"
121 $stderr.puts "Scanned #{num_scanned} / #{num_total} messages and changed #{num_changed}."
123 unless num_changed == 0
124 $stderr.puts "Optimizing index..."
125 index.ferret.optimize unless opts[:dry_run]
128 rescue Exception => e
129 File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }