]> git.cworth.org Git - sup/blobdiff - bin/sup-sync-back
fix parsing of encrypted messages that contain further multipart elements
[sup] / bin / sup-sync-back
old mode 100644 (file)
new mode 100755 (executable)
index c27f192..6298c97
@@ -4,18 +4,26 @@ require 'rubygems'
 require 'uri'
 require 'tempfile'
 require 'trollop'
+require 'enumerator'
 require "sup"
 
 ## save a message 'm' to an open file pointer 'fp'
 def save m, fp
-  m.source.each_raw_full_message_line(m.source_info) { |l| fp.print l }
+  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 }
+  not Enumerable::Enumerator.new(index, :each_id, query).map.empty?
 end
 
 opts = Trollop::options do
   version "sup-sync-back (sup #{Redwood::VERSION})"
   banner <<EOS
-Partially synchronizes a message source with the Sup index by deleting
-or moving any messages from the source that are marked as deleted or
+Drop or move messages from Sup sources that are marked as deleted or
 spam in the Sup index.
 
 Currently only works with mbox sources.
@@ -26,25 +34,39 @@ Usage:
 where <source>* is zero or more source URIs. If no sources are given,
 sync back all usual sources.
 
-You probably want to run sup-sync --changed after this command.
+You almost certainly want to run sup-sync --changed after this command.
+Running this does not change the index.
 
 Options include:
 EOS
-  opt :delete_deleted, "Delete deleted messages.", :default => false, :short => "d"
+  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 :delete_spam, "Delete spam messages.", :default => false, :short => "s"
+  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 :delete_deleted, :move_deleted
-  conflicts :delete_spam, :move_spam
+  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.new
-index.lock_or_die
+index = Redwood::Index.init
+index.lock_interactively or exit
 
 deleted_fp, spam_fp = nil
 unless opts[:dry_run]
@@ -52,59 +74,64 @@ unless opts[:dry_run]
   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 = index.source_for(uri) or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
-    s.is_a?(Redwood::MBox::Loader) or Trollop::die "#{uri} is not an mbox source."
+    s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
+    s.is_a?(Redwood::MBox::Loader) or die "#{uri} is not an mbox source."
     s
   end
 
-  using_usual_sources =
-    if sources.empty?
-      sources = index.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader } 
-      true
-    else
-      false
-    end
+  if sources.empty?
+    sources = Redwood::SourceManager.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader }
+  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
 
-  any_modified = false
-  
+  modified_sources = []
   sources.each do |source|
     $stderr.puts "Scanning #{source}..."
 
-    unless ((opts[:delete_deleted] || opts[:move_deleted]) && index.has_any_from_source_with_label?(source, :deleted)) || ((opts[:delete_spam] || opts[:move_spam]) && index.has_any_from_source_with_label?(source, :spam))
+    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_deleted = num_moved = num_scanned = 0
-    
+    num_dropped = num_moved = num_scanned = 0
+
     out_fp = Tempfile.new "sup-sync-back-#{source.id}"
-    Redwood::PollManager.add_messages_from source do |m, offset, entry|
+    Redwood::PollManager.each_message_from source do |m|
       num_scanned += 1
 
-      if entry
-        labels = entry[:label].split.map { |x| x.intern }.to_boolean_h
+      if(m_old = index.build_message(m.id))
+        labels = m_old.labels
 
         if labels.member? :deleted
-          if opts[:delete_deleted]
-            puts "Dropping deleted message #{source}##{offset}" if opts[:verbose]
-            num_deleted += 1
+          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}##{offset}" if opts[:verbose]
+            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[:delete_spam]
-            puts "Deleting spam message #{source}##{offset}" if opts[:verbose]
-            num_deleted += 1
+          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}##{offset}" if opts[:verbose]
+            puts "Moving spam message #{source}##{m.source_info}" if opts[:verbose]
             save m, spam_fp unless opts[:dry_run]
             num_moved += 1
           end
@@ -114,19 +141,22 @@ begin
       else
         save m, out_fp unless opts[:dry_run]
       end
-
-      nil # don't actually add anything!
     end
-    $stderr.puts "Scanned #{num_scanned}, deleted #{num_deleted}, moved #{num_moved} messages from #{source}."
-    any_modified = true if num_deleted > 0 || num_moved > 0
+    $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] || !any_modified
+    unless opts[:dry_run] || (num_dropped == 0 && num_moved == 0)
       deleted_fp.flush if deleted_fp
       spam_fp.flush if spam_fp
-      out_fp.close
-      $stderr.puts "Moving #{out_fp.path} to #{source.file_path}"
-      FileUtils.mv out_fp.path, source.file_path
+      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
 
@@ -136,18 +166,13 @@ begin
   end
 
   $stderr.puts "Done."
-  if any_modified
-    if using_usual_sources
-      $stderr.puts "You should now run: sup-sync --changed #{sources.join(' ')}"
-    else
-      $stderr.puts "You should now run: sup-sync --changed"
-    end
+  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
-  index.save
   Redwood::finish
   index.unlock
 end