]> git.cworth.org Git - sup/blobdiff - bin/sup-sync-back
Use underline for highlight
[sup] / bin / sup-sync-back
old mode 100644 (file)
new mode 100755 (executable)
index 871af88..6298c97
@@ -4,18 +4,26 @@ require 'rubygems'
 require 'uri'
 require 'tempfile'
 require 'trollop'
 require 'uri'
 require 'tempfile'
 require 'trollop'
+require 'enumerator'
 require "sup"
 
 ## save a message 'm' to an open file pointer 'fp'
 def save m, fp
 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
 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.
 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.
 
 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
 
 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 :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 :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
 
   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
 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]
 
 deleted_fp, spam_fp = nil
 unless opts[:dry_run]
@@ -52,45 +74,64 @@ unless opts[:dry_run]
   spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam]
 end
 
   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|
 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
 
     s
   end
 
-  any_modified = false
-  sources.each { |s| s.reset! }
-  
+  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
+
+  modified_sources = []
   sources.each do |source|
     $stderr.puts "Scanning #{source}..."
   sources.each do |source|
     $stderr.puts "Scanning #{source}..."
-    num_deleted = num_moved = num_scanned = 0
-    
+
+    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}"
     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
 
       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 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)
           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
             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)
           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
             save m, spam_fp unless opts[:dry_run]
             num_moved += 1
           end
@@ -100,18 +141,22 @@ begin
       else
         save m, out_fp unless opts[:dry_run]
       end
       else
         save m, out_fp unless opts[:dry_run]
       end
-
-      nil # don't actually add anything!
     end
     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]
 
     out_fp.close unless opts[:dry_run]
 
-    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
       deleted_fp.flush if deleted_fp
       spam_fp.flush if spam_fp
-      out_fp.close
-      FileUtils.mv out_fp.path, URI(source.uri).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
 
     end
   end
 
@@ -121,12 +166,13 @@ begin
   end
 
   $stderr.puts "Done."
   end
 
   $stderr.puts "Done."
-  $stderr.puts "You should run sup-sync --changed #{sources.join(' ')}." if any_modified
+  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
 rescue Exception => e
   File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
   raise
 ensure
-  index.save
   Redwood::finish
   index.unlock
 end
   Redwood::finish
   index.unlock
 end