]> git.cworth.org Git - sup/commitdiff
many many changes. this is what happens when i have 5 hours on an airplane
authorwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Mon, 26 Nov 2007 20:06:02 +0000 (20:06 +0000)
committerwmorgan <wmorgan@5c8cc53c-5e98-4d25-b20a-d8db53a31250>
Mon, 26 Nov 2007 20:06:02 +0000 (20:06 +0000)
in the middle of the day and no book.

- multi-thread dump upon crash
- hook manager caches values of any proc "variables"
- bugfix: broken source handling improved
- speed up querying
- bugfix: sources sometimes aren't added by sup-add
- more widgets: terminal title, statusbar

git-svn-id: svn://rubyforge.org/var/svn/sup/trunk@722 5c8cc53c-5e98-4d25-b20a-d8db53a31250

17 files changed:
bin/sup
doc/TODO
lib/sup.rb
lib/sup/buffer.rb
lib/sup/hook.rb
lib/sup/index.rb
lib/sup/message.rb
lib/sup/modes/contact-list-mode.rb
lib/sup/modes/edit-message-mode.rb
lib/sup/modes/label-search-results-mode.rb
lib/sup/modes/scroll-mode.rb
lib/sup/modes/thread-index-mode.rb
lib/sup/modes/thread-view-mode.rb
lib/sup/poll.rb
lib/sup/source.rb
lib/sup/suicide.rb
lib/sup/util.rb

diff --git a/bin/sup b/bin/sup
index d779ad0d55f797ab0c17e56dda11a04b599de1c7..d3f8c783ea1e0d4a4eeab1c024143e9d34b2b6d6 100644 (file)
--- a/bin/sup
+++ b/bin/sup
@@ -7,6 +7,7 @@ require 'fileutils'
 require 'trollop'
 require "sup"
 
+$exceptions = []
 $opts = Trollop::options do
   version "sup v#{Redwood::VERSION}"
   banner <<EOS
@@ -172,7 +173,7 @@ begin
 
   Index.usual_sources.each do |s|
     next unless s.respond_to? :connect
-    reporting_thread do
+    reporting_thread("call #connect on #{s}") do
       begin
         s.connect
       rescue SourceError => e
@@ -181,7 +182,7 @@ begin
     end
   end
   
-  imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread { sleep 1; PollManager.poll } unless $opts[:no_threads] }
+  imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] }
 
   unless $opts[:no_threads]
     PollManager.start
@@ -193,7 +194,7 @@ begin
     SearchResultsMode.spawn_from_query $opts[:search]
   end
 
-  until $exception || SuicideManager.die?
+  until $exceptions.nonempty? || SuicideManager.die?
     c = Ncurses.nonblocking_getch
     next unless c
     bm.erase_flash
@@ -215,8 +216,8 @@ begin
       when :list_buffers
         bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
       when :list_contacts
-        b = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
-        b.mode.load_in_background if b
+        b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
+        b.mode.load_in_background if new
       when :search
         query = BufferManager.ask :search, "search all messages: "
         next unless query && query !~ /^\s*$/
@@ -234,7 +235,7 @@ begin
       when :compose
         ComposeMode.spawn_nicely
       when :poll
-        reporting_thread { PollManager.poll }
+        reporting_thread("user-invoked poll") { PollManager.poll }
       when :recall_draft
         case Index.num_results_for :label => :draft
         when 0
@@ -246,8 +247,8 @@ begin
           BufferManager.spawn "Edit message", r
           r.edit_message
         else
-          b = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
-          b.mode.load_threads :num => b.content_height if b
+          b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
+          b.mode.load_threads :num => b.content_height if new
         end
       when :nothing
       when :redraw
@@ -260,7 +261,7 @@ begin
     bm.draw_screen
   end
 rescue Exception => e
-  $exception ||= e
+  $exceptions << [e, "main"]
 ensure
   unless $opts[:no_threads]
     PollManager.stop if PollManager.instantiated?
@@ -276,8 +277,7 @@ ensure
     Redwood::log "I've been ordered to commit sepuku. I obey!"
   end
 
-  case $exception
-  when nil
+  if $exceptions.empty?
     Redwood::log "no fatal errors. good job, william."
     Index.save
   else
@@ -287,27 +287,29 @@ ensure
   Index.unlock
 end
 
-if $exception 
+unless $exceptions.empty?
   File.open("sup-exception-log.txt", "w") do |f|
-    f.puts "--- #{e.class.name} at #{Time.now}"
-    f.puts e.message, e.backtrace
+    $exceptions.each do |e, name|
+      f.puts "--- #{e.class.name} from thread: #{name}"
+      f.puts e.message, e.backtrace
+    end
   end
   $stderr.puts <<EOS
 ----------------------------------------------------------------
-I'm very sorry, but it seems that an error occurred in Sup. 
-Please accept my sincere apologies. If you don't mind, please
-send the backtrace below and a brief report of the circumstances
-to sup-talk at rubyforge dot orgs so that I might address this
-problem. Thank you!
+I'm very sorry. It seems that an error occurred in Sup. Please
+accept my sincere apologies. If you don't mind, please send the
+contents of sup-exception-log.txt and a brief report of the
+circumstances to sup-talk at rubyforge dot orgs so that I might
+address this problem. Thank you!
 
 Sincerely,
 William
 ----------------------------------------------------------------
-
-The problem was: '#{$exception.message}' (error type #{$exception.class.name})
-A backtrace follows:
 EOS
-  raise $exception
+  $exceptions.each do |e, name|
+    puts "--- #{e.class.name} from thread: #{name}"
+    puts e.message, e.backtrace
+  end
 end
 
 end
index 4393a63b1b6d20b0f7c620b4f870bf1a8ab4244b..667125676c3568187cd2f5438d150d443e76a834 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -5,7 +5,6 @@ _ mark thread as unread should remember the unread messages and mark
 _ mark thread as unread should have a version within thread-view-mode
    which then also closes the buffer
 _ bugfix: time zone parsing broken?
-_ mailing list auto-subscribe/unsubscribe
 _ forwards optionally include attachments
 _ attach messages
 _ flesh out gpg integration: sign & encrypt outgoing
@@ -23,9 +22,21 @@ _ search results: highlight relevant snippets and open to relevant
    portion of thread
 _ have "notes" (treated as emails to oneself, never sent) as
    first-class objects.
+x multi-thread dump upon crash
+x hook manager caches values of any proc "variables"
+x bugfix: remove delay on startup if a usual imap source exists
+x bugfix: broken source handling still needs to be improved
+x speed up querying
+x bugfix: sources sometimes aren't added by sup-add
+x more widgets: terminal title, statusbar
+x mailing list auto-subscribe/unsubscribe
 
 future
 ------
+_ ldbd support
+_ don't use a people.txt; store email addresses directly in the index. too many
+  problems with email addresses that occur with multiple names.
+_ infix match instead of prefix match for tab completion (maybe!)
 _ fix killed threads contributing to unread message count problem (prob. need
   to maintain all killed message ids and our own unread message count for
   inbox).
index ac242ecaf905400e8fdfdb0390768f9c29cb8398..f7ce4bf4a86fc29a8d953a81d55209eed3c2a772 100644 (file)
@@ -60,8 +60,7 @@ module Redwood
     end
 
 ## record exceptions thrown in threads nicely
-  $exception = nil
-  def reporting_thread
+  def reporting_thread name
     if $opts[:no_threads]
       yield
     else
@@ -69,7 +68,8 @@ module Redwood
         begin
           yield
         rescue Exception => e
-          $exception ||= e
+          $exceptions ||= []
+          $exceptions << [e, name]
           raise
         end
       end
@@ -120,11 +120,13 @@ module Redwood
   end
 
   ## not really a good place for this, so I'll just dump it here.
+  ##
+  ## a source error is either a FatalSourceError or an OutOfSyncSourceError.
+  ## the superclass SourceError is just a generic.
   def report_broken_sources opts={}
     return unless BufferManager.instantiated?
 
-    broken_sources = Index.usual_sources.select { |s| s.error.is_a? FatalSourceError }
-    File.open("goat", "w") { |f| f.puts Kernel.caller }
+    broken_sources = Index.sources.select { |s| s.error.is_a? FatalSourceError }
     unless broken_sources.empty?
       BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
         TextMode.new(<<EOM)
@@ -141,7 +143,7 @@ EOM
       end
     end
 
-    desynced_sources = Index.usual_sources.select { |s| s.error.is_a? OutOfSyncSourceError }
+    desynced_sources = Index.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
     unless desynced_sources.empty?
       BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
         TextMode.new(<<EOM)
index 4cdfdbb0e875f0d164f07c899712610f9e3a0f2a..8cb12986f0b2b7831ff4edf0b987b7146e7d32a1 100644 (file)
@@ -74,9 +74,13 @@ class Buffer
     mode.resize rows, cols
   end
 
-  def redraw
-    draw if @dirty
-    draw_status
+  def redraw status
+    if @dirty
+      draw status 
+    else
+      draw_status status
+    end
+
     commit
   end
 
@@ -87,9 +91,9 @@ class Buffer
     @w.noutrefresh
   end
 
-  def draw
+  def draw status
     @mode.draw
-    draw_status
+    draw_status status
     commit
   end
 
@@ -110,9 +114,8 @@ class Buffer
     @w.clear
   end
 
-  def draw_status
-    write @height - 1, 0, " [#{mode.name}] #{title}   #{mode.status}",
-      :color => :status_color
+  def draw_status status
+    write @height - 1, 0, status, :color => :status_color
   end
 
   def focus
@@ -138,6 +141,30 @@ class BufferManager
   ## are canceled by any keypress except this one.
   CONTINUE_IN_BUFFER_SEARCH_KEY = "n"
 
+  HookManager.register "status-bar-text", <<EOS
+Sets the status bar. The default status bar contains the mode name, the buffer
+title, and the mode status. Note that this will be called at least once per
+keystroke, so excessive computation is discouraged.
+
+Variables:
+         num_inbox: number of messages in inbox
+  num_inbox_unread: total number of messages marked as unread
+         num_total: total number of messages in the index
+          num_spam: total number of messages marked as spam
+             title: title of the current buffer
+              mode: current mode name (string)
+            status: current mode status (string)
+Return value: a string to be used as the status bar.
+EOS
+
+  HookManager.register "terminal-title-text", <<EOS
+Sets the title of the current terminal, if applicable. Note that this will be
+called at least once per keystroke, so excessive computation is discouraged.
+
+Variables: the same as status-bar-text hook.
+Return value: a string to be used as the terminal title.
+EOS
+
   def initialize
     @name_map = {}
     @buffers = []
@@ -216,16 +243,27 @@ class BufferManager
   def completely_redraw_screen
     return if @shelled
 
+    status, title = get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
+
     Ncurses.sync do
       @dirty = true
       Ncurses.clear
-      draw_screen :sync => false
+      draw_screen :sync => false, :status => status, :title => title
     end
   end
 
   def draw_screen opts={}
     return if @shelled
 
+    status, title =
+      if opts.member? :status
+        [opts[:status], opts[:title]]
+      else
+        get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
+      end
+
+    print "\033]2;#{title}\07" if title
+
     Ncurses.mutex.lock unless opts[:sync] == false
 
     ## disabling this for the time being, to help with debugging
@@ -234,7 +272,7 @@ class BufferManager
     false && @buffers.inject(@dirty) do |dirty, buf|
       buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
       #dirty ? buf.draw : buf.redraw
-      buf.draw
+      buf.draw status
       dirty
     end
 
@@ -242,7 +280,7 @@ class BufferManager
     if true
       buf = @buffers.last
       buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
-      @dirty ? buf.draw : buf.redraw
+      @dirty ? buf.draw(status) : buf.redraw(status)
     end
 
     draw_minibuf :sync => false unless opts[:skip_minibuf]
@@ -253,18 +291,21 @@ class BufferManager
     Ncurses.mutex.unlock unless opts[:sync] == false
   end
 
-  ## gets the mode from the block, which is only called if the buffer
-  ## doesn't already exist. this is useful in the case that generating
-  ## the mode is expensive, as it often is.
+  ## if the named buffer already exists, pops it to the front without
+  ## calling the block. otherwise, gets the mode from the block and
+  ## creates a new buffer. returns two things: the buffer, and a boolean
+  ## indicating whether it's a new buffer or not.
   def spawn_unless_exists title, opts={}
-    if @name_map.member? title
-      raise_to_front @name_map[title] unless opts[:hidden]
-      nil
-    else
-      mode = yield
-      spawn title, mode, opts
-      @name_map[title]
-    end
+    new = 
+      if @name_map.member? title
+        raise_to_front @name_map[title] unless opts[:hidden]
+        false
+      else
+        mode = yield
+        spawn title, mode, opts
+        true
+      end
+    [@name_map[title], new]
   end
 
   def spawn title, mode, opts={}
@@ -647,6 +688,30 @@ class BufferManager
   end
 
 private
+  def default_status_bar buf
+    " [#{buf.mode.name}] #{buf.title}   #{buf.mode.status}"
+  end
+
+  def default_terminal_title buf
+    "Sup #{Redwood::VERSION} :: #{buf.title}"
+  end
+
+  def get_status_and_title buf
+    opts = {
+      :num_inbox => lambda { Index.num_results_for :label => :inbox },
+      :num_inbox_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] },
+      :num_total => lambda { Index.size },
+      :num_spam => lambda { Index.num_results_for :label => :spam },
+      :title => buf.title,
+      :mode => buf.mode.name,
+      :status => buf.mode.status
+    }
+
+    statusbar_text = HookManager.run("status-bar-text", opts) || default_status_bar(buf)
+    term_title_text = HookManager.run("terminal-title-text", opts) || default_terminal_title(buf)
+    
+    [statusbar_text, term_title_text]
+  end
 
   def users
     unless @users
index 327ce2d5b54f4c73d3cb12a4d4b028866b5c3c71..98fcf57bec51116ea85ba265a8117af868bbd1af 100644 (file)
@@ -9,7 +9,7 @@ class HookManager
   ##
   ## i don't bother providing setters, since i'm pretty sure the
   ## charade will fall apart pretty quickly with respect to scoping.
-  ## this is basically fail-fast.
+  ## "fail-fast", we'll call it.
   class HookContext
     def initialize name
       @__say_id = nil
@@ -22,7 +22,7 @@ class HookManager
     def method_missing m, *a
       case @__locals[m]
       when Proc
-        @__locals[m].call(*a)
+        @__locals[m] = @__locals[m].call(*a) # only call the proc once
       when nil
         super
       else
@@ -85,8 +85,8 @@ class HookManager
     rescue Exception => e
       log "error running hook: #{e.message}"
       log e.backtrace.join("\n")
-      BufferManager.flash "Error running hook: #{e.message}"
       @hooks[name] = nil # disable it
+      BufferManager.flash "Error running hook: #{e.message}"
     end
     context.__cleanup
     result
@@ -112,6 +112,8 @@ EOS
     end
   end
 
+  def enabled? name; !hook_for(name).nil? end
+
 private
 
   def hook_for name
index 367829533581323e28b91ff59bec4449e37609af..c2224bc5fd6df1de2a099e4a7e408a15cb3eeaff 100644 (file)
@@ -6,7 +6,7 @@ begin
   require 'chronic'
   $have_chronic = true
 rescue LoadError => e
-  Redwood::log "'chronic' library not found. run 'gem install chronic' to install."
+  Redwood::log "optional 'chronic' library not found (run 'gem install chronic' to install)"
   $have_chronic = false
 end
 
@@ -53,7 +53,7 @@ class Index
   end
 
   def start_lock_update_thread
-    @lock_update_thread = Redwood::reporting_thread do
+    @lock_update_thread = Redwood::reporting_thread("lock update") do
       while true
         sleep 30
         @lock.touch_yourself
@@ -119,8 +119,8 @@ EOS
   def add_source source
     raise "duplicate source!" if @sources.include? source
     @sources_dirty = true
-    source.id ||= @sources.size
-    ##TODO: why was this necessary?
+    max = @sources.max_of { |id, s| s.is_a?(DraftLoader) || s.is_a?(SentLoader) ? 0 : id }
+    source.id ||= (max || 0) + 1
     ##source.id += 1 while @sources.member? source.id
     @sources[source.id] = source
   end
@@ -263,12 +263,14 @@ EOS
     end
 
     until pending.empty? || (opts[:limit] && messages.size >= opts[:limit])
-      id = pending.pop
-      next if searched.member? id
-      searched[id] = true
       q = Ferret::Search::BooleanQuery.new true
-      q.add_query Ferret::Search::TermQuery.new(:message_id, id), :should
-      q.add_query Ferret::Search::TermQuery.new(:refs, id), :should
+
+      pending.each do |id|
+        searched[id] = true
+        q.add_query Ferret::Search::TermQuery.new(:message_id, id), :should
+        q.add_query Ferret::Search::TermQuery.new(:refs, id), :should
+      end
+      pending = []
 
       q = build_query :qobj => q
 
@@ -285,10 +287,11 @@ EOS
           #Redwood::log "got #{mid} as a child of #{id}"
           messages[mid] ||= lambda { build_message docid }
           refs = @index[docid][:refs].split(" ")
-          pending += refs
+          pending += refs.select { |id| !searched[id] }
         end
       end
     end
+
     if killed
       Redwood::log "thread for #{m.id} is killed, ignoring"
       false
index 0ef1a615438a0fce9fa0f4bb2721af13917566b2..8ec8a673730312d5942e2512184d32a001d3481c 100644 (file)
@@ -13,6 +13,10 @@ class MessageFormatError < StandardError; end
 ## i would like, for example, to be able to add in a ruby-talk
 ## specific module that would detect and link to /ruby-talk:\d+/
 ## sequences in the text of an email. (how sweet would that be?)
+##
+## this class cathces all source exceptions. if the underlying source throws
+## an error, it is caught and handled.
+
 class Message
   SNIPPET_LEN = 80
   RE_PATTERN = /^((re|re[\[\(]\d[\]\)]):\s*)+/i
@@ -169,6 +173,7 @@ class Message
           Redwood::log "problem getting messages from #{@source}: #{e.message}"
           ## we need force_to_top here otherwise this window will cover
           ## up the error message one
+          @source.error ||= e
           Redwood::report_broken_sources :force_to_top => true
           [Chunk::Text.new(error_message(e.message))]
         end
@@ -194,11 +199,14 @@ The error message was:
 EOS
   end
 
+  ## wrap any source methods that might throw sourceerrors
   def with_source_errors_handled
     begin
       yield
     rescue SourceError => e
       Redwood::log "problem getting messages from #{@source}: #{e.message}"
+      @source.error ||= e
+      Redwood::report_broken_sources :force_to_top => true
       error_message e.message
     end
   end
index 036369e2fe9e3ed32b313f3947576bbb6a4861cc..399d48ee973e4fa9f991da01fbcd083f927a73b5 100644 (file)
@@ -94,7 +94,7 @@ class ContactListMode < LineCursorMode
   end
 
   def load_in_background
-    Redwood::reporting_thread do
+    Redwood::reporting_thread("contact manager load in bg") do
       load
       update
       BufferManager.draw_screen
index 64ca73dc4446507fa78caf5d9657012b424dc405..e04fc3033dfc4f80f6a9012bba5769cc4625a3fb 100644 (file)
@@ -13,7 +13,7 @@ class EditMessageMode < LineCursorMode
   NON_EDITABLE_HEADERS = %w(Message-Id Date)
 
   HookManager.register "signature", <<EOS
-Generates a signature for a message.
+Generates a message signature.
 Variables:
       header: an object that supports string-to-string hashtable-style access
               to the raw headers for the message. E.g., header["From"],
index 7587ba1972c9e111100d7520fef0b2b8c145caa4..3f4df9db35e587182aba3bce11eef0fe8407e761 100644 (file)
@@ -18,8 +18,8 @@ class LabelSearchResultsMode < ThreadIndexMode
     when :inbox
       BufferManager.raise_to_front InboxMode.instance.buffer
     else
-      b = BufferManager.spawn_unless_exists("All threads with label '#{label}'") { LabelSearchResultsMode.new [label] }
-      b.mode.load_threads :num => b.content_height if b
+      b, new = BufferManager.spawn_unless_exists("All threads with label '#{label}'") { LabelSearchResultsMode.new [label] }
+      b.mode.load_threads :num => b.content_height if new
     end
   end
 end
index 89684bf84e894ad021c8dc96777e55516cfea906..328a07de457cc5e2b00d1f719a2bd3c6f51309d4 100644 (file)
@@ -35,6 +35,7 @@ class ScrollMode < Mode
     @twiddles = opts.member?(:twiddles) ? opts[:twiddles] : true
     @search_query = nil
     @search_line = nil
+    @status = ""
     super()
   end
 
index 1f61f2069a671d005f967cc4f0f22b0bae6c7647..8dec42d1e842b41e217d6bafcf422eb3b1ae3198 100644 (file)
@@ -65,7 +65,6 @@ EOS
 
   def lines; @text.length; end
   def [] i; @text[i]; end
-  #def contains_thread? t; !@lines[t].nil?; end
   def contains_thread? t; @threads.include?(t) end
 
   def reload
@@ -78,8 +77,7 @@ EOS
   def select t=nil
     t ||= cursor_thread or return
 
-    ## TODO: don't regen text completely
-    Redwood::reporting_thread do
+    Redwood::reporting_thread("load messages for thread-view-mode") do
       num = t.size
       message = "Loading #{num.pluralize 'message body'}..."
       BufferManager.say(message) do |sid|
@@ -402,7 +400,7 @@ EOS
 
   def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
     return if @load_thread # todo: wrap in mutex
-    @load_thread = Redwood::reporting_thread do
+    @load_thread = Redwood::reporting_thread("load threads for thread-index-mode") do
       num = load_n_threads n, opts
       opts[:when_done].call(num) if opts[:when_done]
       @load_thread = nil
index ae3042333518dec89a9f6050f6e1c83ed3c8c201..87557093c954a5eb4bc4bbeac8ff94b7179d31c3 100644 (file)
@@ -119,7 +119,7 @@ class ThreadViewMode < LineCursorMode
   def unsubscribe_from_list
     m = @message_lines[curpos] or return
     if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)\?(subject=(.*?))>/
-      spawn_compose_mode :from => AccountManager.account_for(m.recipient_email), :to => [PersonManager.person_for($1)], :subj => $3
+      ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [PersonManager.person_for($1)], :subj => $3
     else
       BufferManager.flash "Can't find List-Unsubscribe header for this message."
     end
@@ -127,7 +127,7 @@ class ThreadViewMode < LineCursorMode
 
   def forward
     m = @message_lines[curpos] or return
-    spawn_forward_mode m
+    ForwardMode.spawn_nicely m
   end
 
   include CanAliasContacts
@@ -147,9 +147,9 @@ class ThreadViewMode < LineCursorMode
   def compose
     p = @person_lines[curpos]
     if p
-      spawn_compose_mode :to => [p]
+      ComposeMode.spawn_nicely :to => [p]
     else
-      spawn_compose_mode
+      ComposeMode.spawn_nicely
     end
   end    
 
index 2faadfe87634d6f1d31bf3ebd0e870d2fa19bb06..387f06e34af5ef7665c2cf3f749ab8c8770b504e 100644 (file)
@@ -39,7 +39,8 @@ EOS
   end
 
   def buffer
-    @buffer ||= BufferManager.spawn_unless_exists("<poll for new messages>", :hidden => true) { PollMode.new }
+    b, new = BufferManager.spawn_unless_exists("<poll for new messages>", :hidden => true) { PollMode.new }
+    b
   end
 
   def poll
@@ -62,7 +63,7 @@ EOS
   end
 
   def start
-    @thread = Redwood::reporting_thread do
+    @thread = Redwood::reporting_thread("periodic poll") do
       while true
         sleep DELAY / 2
         poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
index 50fc16327720a784c1442ab68a0b604d34991e1b..6510aae8a738dba0b3d81acb617312a1e1334bec 100644 (file)
@@ -1,6 +1,11 @@
 module Redwood
 
-class SourceError < StandardError; end
+class SourceError < StandardError
+  def initialize *a
+    raise "don't instantiate me!" if SourceError.is_a?(self.class)
+    super
+  end
+end
 class OutOfSyncSourceError < SourceError; end
 class FatalSourceError < SourceError; end
 
index 7d2738cec175336cc39947321b910d37bda5ce5f..98b43462eea250aa6969221d0a8456b9697bd54a 100644 (file)
@@ -16,7 +16,7 @@ class SuicideManager
   bool_reader :die
 
   def start
-    @thread = Redwood::reporting_thread do
+    @thread = Redwood::reporting_thread("suicide watch") do
       while true
         sleep DELAY
         if File.exists? @fn
index b2a0a4d0552bcc7584ce28faa8858bf55f96d6c3..3ffb92bc39ab04db182873c19268ed7b6a5c19d3 100644 (file)
@@ -399,6 +399,7 @@ class Array
   def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
 
   def last= e; self[-1] = e end
+  def nonempty?; !empty? end
 end
 
 class Time
@@ -505,18 +506,18 @@ module Singleton
   end
 end
 
-## wraps an object. if it throws an exception, keeps a copy, and
-## rethrows it for any further method calls.
+## wraps an object. if it throws an exception, keeps a copy.
 class Recoverable
   def initialize o
     @o = o
-    @e = nil
+    @error = nil
     @mutex = Mutex.new
   end
 
-  def clear_error!; @e = nil; end
-  def has_errors?; !@e.nil?; end
-  def error; @e; end
+  attr_accessor :error
+
+  def clear_error!; @error = nil; end
+  def has_errors?; !@error.nil?; end
 
   def method_missing m, *a, &b; __pass m, *a, &b end
   
@@ -531,8 +532,8 @@ class Recoverable
     begin
       @o.send(m, *a, &b)
     rescue Exception => e
-      @e ||= e
-      raise e
+      @error ||= e
+      raise
     end
   end
 end