sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit ed843833f17338fb99a9b8954981b9ea0ed54694
parent a1669cab5edc11e2260898f4592e7f6a1c488544
Author: William Morgan <wmorgan-sup@masanjin.net>
Date:   Sun,  3 Jan 2010 09:52:03 -0500

Merge branch 'ferret-deprecation' into next

Diffstat:
M bin/sup | 34 +++++++++++++++++++++++++++++++++-
M bin/sup-add | 3 ++-
M bin/sup-config | 2 +-
A bin/sup-convert-ferret-index | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M bin/sup-dump | 5 +++--
M bin/sup-sync | 3 ++-
M bin/sup-sync-back | 3 ++-
M bin/sup-tweak-labels | 3 ++-
M lib/sup.rb | 2 +-
M lib/sup/ferret_index.rb | 2 ++
M lib/sup/index.rb | 34 +++++++++++++++++++++++++++-------
11 files changed, 152 insertions(+), 16 deletions(-)
diff --git a/bin/sup b/bin/sup
@@ -48,6 +48,7 @@ EOS
   opt :search, "Search for this query upon startup", :type => String
   opt :compose, "Compose message to this recipient upon startup", :type => String
   opt :subject, "When composing, use this subject", :type => String, :short => "j"
+  opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
 end
 
 Trollop::die :subject, "requires --compose" if $opts[:subject] && !$opts[:compose]
@@ -147,9 +148,36 @@ def stop_cursing
 end
 module_function :start_cursing, :stop_cursing
 
-Index.init
+Index.init $opts[:index]
 Index.lock_interactively or exit
 
+if Index.is_a_deprecated_ferret_index?
+  FERRET_DEPRECATION_WARNING_FN = File.join BASE_DIR, "you-have-been-warned-about-ferret-deprecation"
+  unless File.exist? FERRET_DEPRECATION_WARNING_FN
+    $stderr.puts <<EOS
+Warning! Your 30-day trial period for using Sup is almost over!
+
+To purchase the full version of Sup, please see http://sup.rubyforge.org/.
+
+Just kidding. BUT! You are using an old Ferret index. The Ferret backend is
+deprecated and support will be removed in the next version of Sup.
+
+You should convert to Xapian before that happens.
+
+The conversion process may take several hours. It is safe and interruptable.
+You can start it at any point by typing:
+
+  sup-convert-ferret-index
+
+Press enter to continue and be on your way. You won't see this message
+again, just a brief reminder at shutdown.
+EOS
+
+    $stdin.gets
+    FileUtils.touch FERRET_DEPRECATION_WARNING_FN
+  end
+end
+
 begin
   Redwood::start
   Index.load
@@ -393,4 +421,8 @@ EOS
   end
 end
 
+if Index.is_a_deprecated_ferret_index?
+  puts "Reminder: to update your Ferret index to Xapian, run sup-convert-ferret-index."
+end
+
 end
diff --git a/bin/sup-add b/bin/sup-add
@@ -40,6 +40,7 @@ EOS
   opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String
   opt :force_new, "Create a new account for this source, even if one already exists."
   opt :force_account, "Reuse previously defined account user@hostname.", :type => String
+  opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
 end
 
 Trollop::die "require one or more sources" if ARGV.empty?
@@ -85,7 +86,7 @@ end
 
 $terminal.wrap_at = :auto
 Redwood::start
-index = Redwood::Index.init
+index = Redwood::Index.init $opts[:index]
 
 index.lock_interactively or exit
 
diff --git a/bin/sup-config b/bin/sup-config
@@ -15,7 +15,7 @@ configuration.
 Usage:
   sup-config
 
-Options:
+No options.
 EOS
 end
 
diff --git a/bin/sup-convert-ferret-index b/bin/sup-convert-ferret-index
@@ -0,0 +1,77 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'trollop'
+require 'sup'
+
+STATE_BACKUP_FN = "/tmp/sup-state.txt"
+SOURCE_BACKUP_FN = "sources.yaml-before-xapian-upgrade"
+BIN_DIR = File.dirname __FILE__
+
+$opts = Trollop::options do
+  version "sup-convert-ferret-index (sup #{Redwood::VERSION})"
+  banner <<EOS
+Convert an Sup Ferret index to a Xapian index.
+
+This will be a very slow process, but it will be lossless.
+
+If you interrupt it, nothing bad will happen. However, you will have to
+restart it from scratch.
+
+Usage:
+  sup-convert-ferret-index
+
+Options:
+EOS
+  opt :verbose, "Be verbose", :short => "-v"
+  opt :dry_run, "Don't actually do anything, just print out what would happen.", :short => "-n"
+  opt :version, "Show version information", :short => :none
+end
+
+def build_cmd cmd
+  (ENV["RUBY_INVOCATION"] ? ENV["RUBY_INVOCATION"] + " " : "") + File.join(BIN_DIR, cmd)
+end
+
+def run cmd
+  puts cmd
+  unless $opts[:dry_run]
+    startt = Time.now
+    system cmd or abort
+    printf "(completed in %.1fs)\n", (Time.now - startt)
+  end
+  puts
+end
+
+Redwood::start
+index = Redwood::Index.init
+Trollop::die "you appear to already have a Xapian index--delete #{File.join(Redwood::BASE_DIR, "xapian")} if you really want to do this" unless Redwood::Index.is_a_deprecated_ferret_index?
+
+puts "## Step one: back up all message state to #{STATE_BACKUP_FN}"
+run "#{build_cmd 'sup-dump'} --index ferret > #{STATE_BACKUP_FN}"
+puts "## message state saved to #{STATE_BACKUP_FN}"
+
+source_backup_fn = File.join Redwood::BASE_DIR, SOURCE_BACKUP_FN
+puts "## Step two: back up sources.yaml file to #{source_backup_fn}"
+run "cp #{Redwood::SOURCE_FN} #{source_backup_fn}"
+
+puts "## Step three: build the Xapian index"
+run "#{build_cmd 'sup-sync'} --all --all-sources --index xapian --restore #{STATE_BACKUP_FN} #{$opts[:verbose] ? '--verbose' : ''}"
+puts "## xapian index successfully built!"
+
+puts <<EOS
+
+Congratulations, your index has been upgraded to the Xapian backend.
+From now on, running sup should detect this index automatically.
+
+If you want to revert to the Ferret index:
+1. overwrite #{Redwood::SOURCE_FN} with #{source_backup_fn}
+2. use sup --index ferret, OR delete your #{Redwood::BASE_DIR}/xapian directory"
+Note that the Ferret index will not be supported as of the next Sup release, so
+you probably shouldn't do this.
+
+If you are happy with Xapian and want to reclaim precious hard drive space:
+1. rm #{source_backup_fn}
+2. rm -r #{Redwood::BASE_DIR}/ferret
+
+Happy supping!
+EOS
diff --git a/bin/sup-dump b/bin/sup-dump
@@ -17,11 +17,12 @@ Usage:
   sup-dump > <filename>
   sup-dump | bzip2 > <filename> # even better
 
-No options.
+Options:
 EOS
+  opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
 end
 
-index = Redwood::Index.init
+index = Redwood::Index.init $opts[:index]
 Redwood::SourceManager.init
 index.load
 
diff --git a/bin/sup-sync b/bin/sup-sync
@@ -76,6 +76,7 @@ text <<EOS
 
 Other options:
 EOS
+  opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
   opt :verbose, "Print message ids as they're processed."
   opt :optimize, "As the final operation, optimize the index."
   opt :all_sources, "Scan over all sources.", :short => :none
@@ -95,7 +96,7 @@ target = [:new, :changed, :all, :restored].find { |x| opts[x] } || :new
 op = [:asis, :restore, :discard].find { |x| opts[x] } || :asis
 
 Redwood::start
-index = Redwood::Index.init
+index = Redwood::Index.init opts[:index]
 
 restored_state = if opts[:restore]
   dump = {}
diff --git a/bin/sup-sync-back b/bin/sup-sync-back
@@ -47,6 +47,7 @@ EOS
   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 :index, "Use this index type ('auto' for autodetect)", :default => "auto"
   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
@@ -65,7 +66,7 @@ EOS
 end
 
 Redwood::start
-index = Redwood::Index.init
+index = Redwood::Index.init $opts[:index]
 index.lock_interactively or exit
 
 deleted_fp, spam_fp = nil
diff --git a/bin/sup-tweak-labels b/bin/sup-tweak-labels
@@ -46,6 +46,7 @@ EOS
 
 Other options:
 EOS
+  opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
   opt :verbose, "Print message ids as they're processed."
   opt :very_verbose, "Print message names and subjects as they're processed."
   opt :all_sources, "Scan over all sources.", :short => :none
@@ -60,7 +61,7 @@ remove_labels = opts[:remove].to_set_of_symbols ","
 Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
 
 Redwood::start
-index = Redwood::Index.init
+index = Redwood::Index.init $opts[:index]
 index.lock_interactively or exit
 begin
   index.load
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -54,7 +54,7 @@ module Redwood
   YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
 
-  DEFAULT_INDEX = 'ferret'
+  DEFAULT_NEW_INDEX_TYPE = 'xapian'
 
   ## record exceptions thrown in threads nicely
   @exceptions = []
diff --git a/lib/sup/ferret_index.rb b/lib/sup/ferret_index.rb
@@ -11,6 +11,8 @@ Variables:
   subs: The string being searched.
 EOS
 
+  def is_a_deprecated_ferret_index?; true end
+
   def initialize dir=BASE_DIR
     super
 
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
@@ -23,6 +23,8 @@ class BaseIndex
     def method_missing m; @h[m.to_s] end
   end
 
+  def is_a_deprecated_ferret_index?; false end
+
   include Singleton
 
   def initialize dir=BASE_DIR
@@ -207,13 +209,31 @@ class BaseIndex
   end
 end
 
-index_name = ENV['SUP_INDEX'] || $config[:index] || DEFAULT_INDEX
-case index_name
-  when "xapian"; require "sup/xapian_index"
-  when "ferret"; require "sup/ferret_index"
-  else fail "unknown index type #{index_name.inspect}"
+## just to make the backtraces even more insane, here we engage in yet more
+## method_missing metaprogramming so that Index.init(index_type_name) will
+## magically make Index act like the correct Index class.
+class Index
+  def self.init type=nil
+    ## determine the index type from the many possible ways of setting it
+    type = (type == "auto" ? nil : type) ||
+      ENV['SUP_INDEX'] ||
+      $config[:index] ||
+      (File.exist?(File.join(BASE_DIR, "xapian")) && "xapian") || ## PRIORITIZE THIS
+      (File.exist?(File.join(BASE_DIR, "ferret")) && "ferret") || ## deprioritize this
+      DEFAULT_NEW_INDEX_TYPE
+    begin
+      require "sup/#{type}_index"
+      @klass = Redwood.const_get "#{type.capitalize}Index"
+      @obj = @klass.init
+    rescue LoadError, NameError => e
+      raise "unknown index type #{type.inspect}: #{e.message}"
+    end
+    debug "using #{type} index"
+    @obj
+  end
+
+  def self.instance; @obj end
+  def self.method_missing m, *a, &b; @obj.send(m, *a, &b) end
 end
-Index = Redwood.const_get "#{index_name.capitalize}Index"
-debug "using index #{Index.name}"
 
 end