#!/usr/bin/env ruby require 'rubygems' require 'uri' require 'tempfile' require 'trollop' require "sup"; Redwood::check_library_version_against "0.13.0" fail "not working yet" ## save a message 'm' to an open file pointer 'fp' def save m, fp m.source.each_raw_message_line(m.source_info) { |l| fp.print l } end def die msg $stderr.puts "Error: #{msg}" exit(-1) end def has_any_from_source_with_label? index, source, label query = { :source_id => source.id, :label => label, :limit => 1, :load_spam => true, :load_deleted => true, :load_killed => true } index.num_results_for(query) != 0 end opts = Trollop::options do version "sup-sync-back (sup #{Redwood::VERSION})" banner <<EOS Drop or move messages from Sup sources that are marked as deleted or spam in the Sup index. Currently only works with mbox sources. Usage: sup-sync-back [options] <source>* where <source>* is zero or more source URIs. If no sources are given, sync back all usual sources. You almost certainly want to run sup-sync --changed after this command. Running this does not change the index. Options include: EOS opt :drop_deleted, "Drop deleted messages.", :default => false, :short => "d" opt :move_deleted, "Move deleted messages to a local mbox file.", :type => String, :short => :none opt :drop_spam, "Drop spam messages.", :default => false, :short => "s" opt :move_spam, "Move spam messages to a local mbox file.", :type => String, :short => :none opt :with_dotlockfile, "Specific dotlockfile location (mbox files only).", :default => "/usr/bin/dotlockfile", :short => :none opt :dont_use_dotlockfile, "Don't use dotlockfile to lock mbox files. Dangerous if other processes modify them concurrently.", :default => false, :short => :none opt :verbose, "Print message ids as they're processed." opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n" opt :version, "Show version information", :short => :none conflicts :drop_deleted, :move_deleted conflicts :drop_spam, :move_spam end unless opts[:drop_deleted] || opts[:move_deleted] || opts[:drop_spam] || opts[:move_spam] puts <<EOS Nothing to do. Please specify at least one of --drop-deleted, --move-deleted, --drop-spam, or --move-spam. EOS exit end Redwood::start index = Redwood::Index.init index.lock_interactively or exit deleted_fp, spam_fp = nil unless opts[:dry_run] deleted_fp = File.open(opts[:move_deleted], "a") if opts[:move_deleted] spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam] end dotlockfile = opts[:with_dotlockfile] || "/usr/bin/dotlockfile" begin index.load sources = ARGV.map do |uri| s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?" s.is_a?(Redwood::MBox) or die "#{uri} is not an mbox source." s end if sources.empty? sources = Redwood::SourceManager.usual_sources.select { |s| s.is_a? Redwood::MBox } end unless sources.all? { |s| s.file_path.nil? } || File.executable?(dotlockfile) || opts[:dont_use_dotlockfile] die <<EOS can't execute dotlockfile binary: #{dotlockfile}. Specify --with-dotlockfile if it's in a nonstandard location, or, if you want to live dangerously, try --dont-use-dotlockfile EOS end modified_sources = [] sources.each do |source| $stderr.puts "Scanning #{source}..." unless ((opts[:drop_deleted] || opts[:move_deleted]) && has_any_from_source_with_label?(index, source, :deleted)) || ((opts[:drop_spam] || opts[:move_spam]) && has_any_from_source_with_label?(index, source, :spam)) $stderr.puts "Nothing to do from this source; skipping" next end source.reset! num_dropped = num_moved = num_scanned = 0 out_fp = Tempfile.new "sup-sync-back-#{source.id}" Redwood::PollManager.each_message_from source do |m| num_scanned += 1 if(m_old = index.build_message(m.id)) labels = m_old.labels if labels.member? :deleted if opts[:drop_deleted] puts "Dropping deleted message #{source}##{m.source_info}" if opts[:verbose] num_dropped += 1 elsif opts[:move_deleted] && labels.member?(:deleted) puts "Moving deleted message #{source}##{m.source_info}" if opts[:verbose] save m, deleted_fp unless opts[:dry_run] num_moved += 1 end elsif labels.member? :spam if opts[:drop_spam] puts "Dropping spam message #{source}##{m.source_info}" if opts[:verbose] num_dropped += 1 elsif opts[:move_spam] && labels.member?(:spam) puts "Moving spam message #{source}##{m.source_info}" if opts[:verbose] save m, spam_fp unless opts[:dry_run] num_moved += 1 end else save m, out_fp unless opts[:dry_run] end else save m, out_fp unless opts[:dry_run] end end $stderr.puts "Scanned #{num_scanned}, dropped #{num_dropped}, moved #{num_moved} messages from #{source}." modified_sources << source if num_dropped > 0 || num_moved > 0 out_fp.close unless opts[:dry_run] unless opts[:dry_run] || (num_dropped == 0 && num_moved == 0) deleted_fp.flush if deleted_fp spam_fp.flush if spam_fp unless opts[:dont_use_dotlockfile] puts "Locking #{source.file_path}..." system "#{opts[:dotlockfile]} -l #{source.file_path}" puts "Writing #{source.file_path}..." FileUtils.cp out_fp.path, source.file_path puts "Unlocking #{source.file_path}..." system "#{opts[:dotlockfile]} -u #{source.file_path}" end end end unless opts[:dry_run] deleted_fp.close if deleted_fp spam_fp.close if spam_fp end $stderr.puts "Done." unless modified_sources.empty? $stderr.puts "You should now run: sup-sync --changed #{modified_sources.join(' ')}" end rescue Exception => e File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace } raise ensure Redwood::finish index.unlock end