ever seen. this will probably need to be intelligently trimmed, but will be
  used for fast loading of messages from slow sources.
- moved initialization and shutdown to Redwood::start and ::finish in sup.rb.
- poll#initialize no longer automatically starts a thread. you must call
  #start_thread. (because sup-import now calls Redwood::start which initializes
  all managers, including pollmanager, and it certainly doesn't make sense to
  have anything threaded during import)
- misc. comment improvements
git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@113 
5c8cc53c-5e98-4d25-b20a-
d8db53a31250
 
 end
 module_function :start_cursing, :stop_cursing
 
-Redwood::SentManager.new Redwood::SENT_FN
-Redwood::ContactManager.new Redwood::CONTACT_FN
-Redwood::LabelManager.new Redwood::LABEL_FN
-Redwood::AccountManager.new $config[:accounts]
-Redwood::DraftManager.new Redwood::DRAFT_DIR
-Redwood::UpdateManager.new
-Redwood::PollManager.new
+Redwood::start
 
 Index.new.load
 log "loaded #{Index.size} messages from index"
 else
   Index.add_source SentManager.new_source
 end
-  
+
 begin
   log "starting curses"
   start_cursing
   imode.load_more_threads ibuf.content_height
 
   reporting_thread { sleep 3; PollManager.poll }
+  PollManager.start_thread
 
   until $exception
     bm.draw_screen
     end
   end
   bm.kill_all_buffers
-  Redwood::LabelManager.save
-  Redwood::ContactManager.save
 rescue Exception => e
   $exception ||= e
 ensure
+  Redwood::finish
   stop_cursing
 end
 
 
   educate_user
 end
 
+Redwood::start
+
 puts "loading index..."
 index = Redwood::Index.new
 index.load
   end
 ensure
   index.save
+  Redwood::finish
 end
 
 if rebuild || force_rebuild
 
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
   SOURCE_FN  = File.join(BASE_DIR, "sources.yaml")
   LABEL_FN   = File.join(BASE_DIR, "labels.txt")
+  PERSON_FN   = File.join(BASE_DIR, "people.txt")
   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
     end
   end
 
-  module_function :register_yaml, :save_yaml_obj, :load_yaml_obj
+  def start
+    Redwood::PersonManager.new Redwood::PERSON_FN
+    Redwood::SentManager.new Redwood::SENT_FN
+    Redwood::ContactManager.new Redwood::CONTACT_FN
+    Redwood::LabelManager.new Redwood::LABEL_FN
+    Redwood::AccountManager.new $config[:accounts]
+    Redwood::DraftManager.new Redwood::DRAFT_DIR
+    Redwood::UpdateManager.new
+    Redwood::PollManager.new
+  end
+
+  def finish
+    Redwood::LabelManager.save
+    Redwood::ContactManager.save
+    Redwood::PersonManager.save
+  end
+
+  module_function :register_yaml, :save_yaml_obj, :load_yaml_obj, :start, :finish
 end
 
 ## set up default configuration file
 
 class LabelManager
   include Singleton
 
-  ## all labels that have special meaning. user will be unable to
+  ## labels that have special semantics. user will be unable to
   ## add/remove these via normal label mechanisms.
   RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent ]
 
 
 module Redwood
 
+class PersonManager
+  include Singleton
+
+  def initialize fn
+    @fn = fn
+    @names = {}
+    IO.readlines(fn).map { |l| l =~ /^(.*)?:\s+(.*)$/ && @names[$1] = $2 } if File.exists? fn
+    self.class.i_am_the_instance self
+  end
+
+  def name_for email; @names[email]; end
+  def register email, name
+    return unless name
+
+    name = name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
+
+    ## all else being equal, prefer longer names, unless the prior name
+    ## doesn't contain any capitalization
+    oldname = @names[email]
+    @names[email] = name if oldname.nil? || oldname.length < name.length || (oldname !~ /[A-Z]/ && name =~ /[A-Z]/)
+  end
+
+  def save; File.open(@fn, "w") { |f| @names.each { |email, name| f.puts "#{email}: #{name}" } }; end
+end
+
 class Person
   @@email_map = {}
 
 
   def initialize name, email
     raise ArgumentError, "email can't be nil" unless email
-    @name = 
-      if name
-        name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
-      else
-        nil
-      end
     @email = email.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ").downcase
-    @@email_map[@email] = self
+    PersonManager.register @email, name
+    @name = PersonManager.name_for @email
   end
 
   def == o; o && o.email == email; end
   alias :eql? :==
-
-  def hash
-    [name, email].hash
-  end
+  def hash; [name, email].hash; end
 
   def shortname
     case @name
     when /(\S+) \S+/
       $1
     when nil
-      @email #[0 ... 10]
+      @email
     else
-      @name #[0 ... 10]
+      @name
     end
   end
 
     end
   end
 
-  def mediumname
-    if @name
-      name
-    else
-      @email
-    end
-  end
+  def mediumname; @name || @email; end
 
   def full_address
     if @name && @email
       if @name =~ /"/
-        "#{@name.inspect} <#@email>"
+        "#{@name.inspect} <#@email>" # escape quotes
       else
         "#@name <#@email>"
       end
     end
   end
 
+  ## when sorting addresses, sort by this 
   def sort_by_me
     case @name
     when /^(\S+), \S+/
     end.downcase
   end
 
-  def self.for_several s
-    return [] if s.nil?
-
-    begin
-      s.split_on_commas.map { |ss| self.for ss }
-    rescue StandardError => e
-      raise "#{e.message}: for #{s.inspect}"
-    end
-  end
-
   def self.for s
     return nil if s.nil?
+
+    ## try and parse an email address and name
     name, email =
       case s
       when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
         [nil, s]
       end
 
-    if name && (p = @@email_map[email])
-      ## all else being equal, prefer longer names, unless the prior name
-      ## doesn't contain any capitalization
-      p.name = name if (p.name.nil? || p.name.length < name.length) unless
-        p.name =~ /[A-Z]/ || (AccountManager.instantiated? && AccountManager.is_account?(p))
-      p 
-    else
-      Person.new name, email
+    @@email_map[email] ||= Person.new name, email
+  end
+
+  def self.for_several s
+    return [] if s.nil?
+
+    begin
+      s.split_on_commas.map { |ss| self.for ss }
+    rescue StandardError => e
+      raise "#{e.message}: for #{s.inspect}"
     end
   end
 end
 
     @last_poll = nil
     
     self.class.i_am_the_instance self
-
-    Redwood::reporting_thread do
-      while true
-        sleep DELAY / 2
-        poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
-      end
-    end
   end
 
   def buffer
     [num, numi]
   end
 
+  def start_thread
+    Redwood::reporting_thread do
+      while true
+        sleep DELAY / 2
+        poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
+      end
+    end
+  end
+
   def do_poll
     return [0, 0] if @polling
     @polling = true
 
     self[0 .. 0].upcase + self[1 .. -1]
   end
 
-  ## found on teh internets
+  ## a very complicated regex found on teh internets to split on
+  ## commas, unless they occurr within double quotes.
   def split_on_commas
     split(/,\s*(?=(?:[^"]*"[^"]*")*(?![^"]*"))/)
   end