sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 2086cffc0a47bd3e115454c8fc0310500e247383
parent c3e15c24b0476d608af8be9ff88e9329d6abad7e
Author: Rich Lane <rlane@club.cc.cmu.edu>
Date:   Wed, 19 Jan 2011 20:05:19 -0800

Merge branch 'master' into next

Diffstat:
A bin/sup-import-dump | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M bin/sup-server | 0
M lib/sup/index.rb | 15 +++++++++++++++
3 files changed, 114 insertions(+), 0 deletions(-)
diff --git a/bin/sup-import-dump b/bin/sup-import-dump
@@ -0,0 +1,99 @@
+#!/usr/bin/env ruby
+
+require 'uri'
+require 'rubygems'
+require 'trollop'
+require "sup"; Redwood::check_library_version_against "git"
+
+PROGRESS_UPDATE_INTERVAL = 15 # seconds
+
+class AbortExecution < SystemExit
+end
+
+opts = Trollop::options do
+  version "sup-import-dump (sup #{Redwood::VERSION})"
+  banner <<EOS
+Imports message state previously exported by sup-dump into the index.
+sup-import-dump operates on the index only, so the messages must have already
+been added using sup-sync. If you need to recreate the index, see sup-sync
+--restore <filename> instead.
+
+Messages not mentioned in the dump file will not be modified.
+
+Usage:
+  sup-import-dump [options] <dump file>
+
+Options:
+EOS
+  opt :verbose, "Print message ids as they're processed."
+  opt :ignore_missing, "Silently skip over messages that are not in the index."
+  opt :warn_missing, "Warn about messages that are not in the index, but continue."
+  opt :abort_missing, "Abort on encountering messages that are not in the index. (default)"
+  opt :atomic, "Use transaction to apply all changes atomically."
+  opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
+  opt :version, "Show version information", :short => :none
+
+  conflicts :ignore_missing, :warn_missing, :abort_missing
+end
+Trollop::die "No dump file given" if ARGV.empty?
+Trollop::die "Extra arguments given" if ARGV.length > 1
+dump_name = ARGV.shift
+missing_action = [:ignore_missing, :warn_missing, :abort_missing].find { |x| opts[x] } || :abort_missing
+
+Redwood::start
+index = Redwood::Index.init
+
+index.lock_interactively or exit
+begin
+  num_read = 0
+  num_changed = 0
+  index.load
+  index.begin_transaction if opts[:atomic]
+
+  IO.foreach dump_name do |l|
+    l =~ /^(\S+) \((.*?)\)$/ or raise "Can't read dump line: #{l.inspect}"
+    mid, labels = $1, $2
+    num_read += 1
+
+    unless index.contains_id? mid
+      if missing_action == :abort_missing
+        $stderr.puts "Message #{mid} not found in index, aborting."
+        raise AbortExecution, 10
+      elsif missing_action == :warn_missing
+        $stderr.puts "Message #{mid} not found in index, skipping."
+      end
+
+      next
+    end
+
+    m = index.build_message mid
+    new_labels = labels.to_set_of_symbols
+
+    if m.labels == new_labels
+      puts "#{mid} unchanged" if opts[:verbose]
+      next
+    end
+
+    puts "Changing flags for #{mid} from '#{m.labels.to_a * ' '}' to '#{new_labels.to_a * ' '}'" if opts[:verbose]
+    num_changed += 1
+
+    next if opts[:dry_run]
+
+    m.labels = new_labels
+    index.update_message_state m
+  end
+
+  index.commit_transaction if opts[:atomic]
+  puts "Updated #{num_changed} of #{num_read} messages."
+rescue AbortExecution
+  index.cancel_transaction if opts[:atomic]
+  raise
+rescue Exception => e
+  index.cancel_transaction if opts[:atomic]
+  File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
+  raise
+ensure
+  index.save_index unless opts[:atomic]
+  Redwood::finish
+  index.unlock
+end
diff --git a/bin/sup-server b/bin/sup-server
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
@@ -260,6 +260,21 @@ EOS
     end
   end
 
+  # wrap all future changes inside a transaction so they're done atomically
+  def begin_transaction
+    synchronize { @xapian.begin_transaction }
+  end
+
+  # complete the transaction and write all previous changes to disk
+  def commit_transaction
+    synchronize { @xapian.commit_transaction }
+  end
+
+  # abort the transaction and revert all changes made since begin_transaction
+  def cancel_transaction
+    synchronize { @xapian.cancel_transaction }
+  end
+
   ## xapian-compact takes too long, so this is a no-op
   ## until we think of something better
   def optimize