]> git.cworth.org Git - sup/commitdiff
Merge branch 'various-api-refactors' into next
authorWilliam Morgan <wmorgan-sup@masanjin.net>
Wed, 19 Aug 2009 18:34:34 +0000 (14:34 -0400)
committerWilliam Morgan <wmorgan-sup@masanjin.net>
Wed, 19 Aug 2009 18:34:34 +0000 (14:34 -0400)
45 files changed:
Manifest.txt
bin/sup
bin/sup-add
bin/sup-config
bin/sup-dump
bin/sup-recover-sources
bin/sup-sync
bin/sup-sync-back
bin/sup-tweak-labels
lib/sup.rb
lib/sup/account.rb
lib/sup/buffer.rb
lib/sup/colormap.rb
lib/sup/contact.rb
lib/sup/crypto.rb
lib/sup/draft.rb
lib/sup/ferret_index.rb
lib/sup/hook.rb
lib/sup/imap.rb
lib/sup/index.rb
lib/sup/interactive-lock.rb [new file with mode: 0644]
lib/sup/label.rb
lib/sup/logger.rb
lib/sup/maildir.rb
lib/sup/mbox.rb
lib/sup/mbox/ssh-file.rb
lib/sup/message-chunks.rb
lib/sup/message.rb
lib/sup/mode.rb
lib/sup/modes/console-mode.rb [new file with mode: 0644]
lib/sup/modes/edit-message-mode.rb
lib/sup/modes/label-list-mode.rb
lib/sup/modes/log-mode.rb
lib/sup/modes/poll-mode.rb
lib/sup/modes/reply-mode.rb
lib/sup/modes/thread-view-mode.rb
lib/sup/poll.rb
lib/sup/sent.rb
lib/sup/source.rb
lib/sup/suicide.rb [deleted file]
lib/sup/textfield.rb
lib/sup/undo.rb
lib/sup/update.rb
lib/sup/util.rb
lib/sup/xapian_index.rb

index be633d776e2b1e5165edeac4d7631cf714471207..09d867eb47595a3ad8a1a7e693734e9f201cb983 100644 (file)
@@ -32,6 +32,7 @@ lib/sup/index.rb
 lib/sup/keymap.rb
 lib/sup/label.rb
 lib/sup/logger.rb
+lib/sup/interactive-lock.rb
 lib/sup/maildir.rb
 lib/sup/mbox.rb
 lib/sup/mbox/loader.rb
@@ -67,7 +68,6 @@ lib/sup/poll.rb
 lib/sup/rfc2047.rb
 lib/sup/sent.rb
 lib/sup/source.rb
-lib/sup/suicide.rb
 lib/sup/tagger.rb
 lib/sup/textfield.rb
 lib/sup/thread.rb
diff --git a/bin/sup b/bin/sup
index 786fba2dadf6f0257b595a51c8a4e9c1d7c249e6..3d5b6c1da0207cd0ec41b8756522f3a4c6880a4b 100755 (executable)
--- a/bin/sup
+++ b/bin/sup
@@ -78,6 +78,8 @@ global_keymap = Keymap.new do |k|
   k.add :compose, "Compose new message", 'm', 'c'
   k.add :nothing, "Do nothing", :ctrl_g
   k.add :recall_draft, "Edit most recent draft message", 'R'
+  k.add :show_inbox, "Show the Inbox buffer", 'I'
+  k.add :show_console, "Show the Console buffer", '~'
 end
 
 ## the following magic enables wide characters when used with a ruby
@@ -95,17 +97,17 @@ module LibC
     else; "libc.so.6"
   end
 
-  Redwood::log "dynamically loading setlocale() from #{setlocale_lib}"
+  debug "dynamically loading setlocale() from #{setlocale_lib}"
   begin
     dlload setlocale_lib
     extern "void setlocale(int, const char *)"
-    Redwood::log "setting locale..."
+    debug "setting locale..."
     LibC.setlocale(6, "")  # LC_ALL == 6
   rescue RuntimeError => e
-    Redwood::log "cannot dlload setlocale(); ncurses wide character support probably broken."
-    Redwood::log "dlload error was #{e.class}: #{e.message}"
+    warn "cannot dlload setlocale(); ncurses wide character support probably broken."
+    warn "dlload error was #{e.class}: #{e.message}"
     if Config::CONFIG['arch'] =~ /bsd/
-      Redwood::log "BSD variant detected. You may have to install a compat6x package to acquire libc."
+      warn "BSD variant detected. You may have to install a compat6x package to acquire libc."
     end
   end
 end
@@ -129,41 +131,21 @@ def stop_cursing
 end
 module_function :start_cursing, :stop_cursing
 
-Index.new
-begin
-  Index.lock
-rescue Index::LockError => e
-  require 'highline'
-
-  h = HighLine.new
-  h.wrap_at = :auto
-  h.say Index.fancy_lock_error_message_for(e)
-
-  case h.ask("Should I ask that process to kill itself? ")
-  when /^\s*y(es)?\s*$/i
-    h.say "Ok, suggesting seppuku..."
-    FileUtils.touch Redwood::SUICIDE_FN
-    sleep SuicideManager::DELAY * 2
-    FileUtils.rm_f Redwood::SUICIDE_FN
-    h.say "Let's try that again."
-    retry
-  else
-    h.say <<EOS
-Ok, giving up. If the process crashed and left a stale lockfile, you
-can fix this by manually deleting #{Index.lockfile}.
-EOS
-    exit
-  end
-end
+Index.init
+Index.lock_interactively or exit
 
 begin
   Redwood::start
   Index.load
 
+  $die = false
+  trap("TERM") { |x| $die = true }
+  trap("WINCH") { |x| BufferManager.sigwinch_happened! }
+
   if(s = Redwood::SourceManager.source_for DraftManager.source_name)
     DraftManager.source = s
   else
-    Redwood::log "no draft source, auto-adding..."
+    debug "no draft source, auto-adding..."
     Redwood::SourceManager.add_source DraftManager.new_source
   end
 
@@ -175,18 +157,24 @@ begin
 
   HookManager.run "startup"
 
-  log "starting curses"
+  debug "starting curses"
+  Redwood::Logger.remove_sink $stderr
   start_cursing
 
-  bm = BufferManager.new
+  bm = BufferManager.init
   Colormap.new.populate_colormap
 
-  log "initializing mail index buffer"
+  debug "initializing log buffer"
+  lmode = Redwood::LogMode.new "system log"
+  lmode.on_kill { Logger.clear! }
+  Logger.add_sink lmode
+  Logger.force_message "Welcome to Sup! Log level is set to #{Logger.level}."
+
+  debug "initializing inbox buffer"
   imode = InboxMode.new
   ibuf = bm.spawn "Inbox", imode
 
-  log "ready for interaction!"
-  Logger.make_buf
+  debug "ready for interaction!"
 
   bm.draw_screen
 
@@ -196,7 +184,7 @@ begin
       begin
         s.connect
       rescue SourceError => e
-        Redwood::log "fatal error loading from #{s}: #{e.message}"
+        error "fatal error loading from #{s}: #{e.message}"
       end
     end
   end unless $opts[:no_initial_poll]
@@ -209,7 +197,6 @@ begin
 
   unless $opts[:no_threads]
     PollManager.start
-    SuicideManager.start
     Index.start_lock_update_thread
   end
 
@@ -217,18 +204,29 @@ begin
     SearchResultsMode.spawn_from_query $opts[:search]
   end
 
-  until Redwood::exceptions.nonempty? || SuicideManager.die?
-    c = 
-       begin
-         Ncurses.nonblocking_getch
-       rescue Exception => e
-         if e.is_a?(Interrupt)
-           raise if BufferManager.ask_yes_or_no("Die ungracefully now?")
-           bm.draw_screen
-           nil
-         end
-       end
-    next unless c
+  until Redwood::exceptions.nonempty? || $die
+    c = begin
+      Ncurses.nonblocking_getch
+    rescue Interrupt => e
+      raise if BufferManager.ask_yes_or_no "Die ungracefully now?"
+      BufferManager.draw_screen
+      nil
+    end
+
+    if c.nil?
+      if BufferManager.sigwinch_happened?
+        debug "redrawing screen on sigwinch"
+        BufferManager.completely_redraw_screen
+      end
+      next
+    end
+
+    if c == 410
+      ## this is ncurses's way of telling us it's detected a refresh.
+      ## since we have our own sigwinch handler, we don't do anything.
+      next
+    end
+
     bm.erase_flash
 
     action =
@@ -296,6 +294,11 @@ begin
         b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
         b.mode.load_threads :num => b.content_height if new
       end
+    when :show_inbox
+      BufferManager.raise_to_front ibuf
+    when :show_console
+      b, new = bm.spawn_unless_exists("Console", :system => true) { ConsoleMode.new }
+      b.mode.run
     when :nothing, InputSequenceAborted
     when :redraw
       bm.completely_redraw_screen
@@ -306,13 +309,12 @@ begin
     bm.draw_screen
   end
 
-  bm.kill_all_buffers if SuicideManager.die?
+  bm.kill_all_buffers if $die
 rescue Exception => e
   Redwood::record_exception e, "main"
 ensure
   unless $opts[:no_threads]
     PollManager.stop if PollManager.instantiated?
-    SuicideManager.stop if PollManager.instantiated?
     Index.stop_lock_update_thread
   end
 
@@ -320,17 +322,19 @@ ensure
 
   Redwood::finish
   stop_cursing
-  Redwood::log "stopped cursing"
+  Redwood::Logger.remove_all_sinks!
+  Redwood::Logger.add_sink $stderr, false
+  debug "stopped cursing"
 
-  if SuicideManager.instantiated? && SuicideManager.die?
-    Redwood::log "I've been ordered to commit seppuku. I obey!"
+  if $die
+    info "I've been ordered to commit seppuku. I obey!"
   end
 
   if Redwood::exceptions.empty?
-    Redwood::log "no fatal errors. good job, william."
+    debug "no fatal errors. good job, william."
     Index.save
   else
-    Redwood::log "oh crap, an exception"
+    error "oh crap, an exception"
   end
 
   Index.unlock
index 3ab7c4db0a18bf4bdb07bf2b9b7f9af375172e78..e27a0ebf6ff446a9347d4fd8937653ab45b97db8 100755 (executable)
@@ -77,9 +77,9 @@ end
 
 $terminal.wrap_at = :auto
 Redwood::start
-index = Redwood::Index.new
+index = Redwood::Index.init
 
-index.lock_or_die
+index.lock_interactively or exit
 
 begin
   Redwood::SourceManager.load_sources
index 9fcbee60e527250700b6aaaf0e64a4746b22dda2..bd55fc1825fad84b6cf6cc8c0185d52effbc5adf 100755 (executable)
@@ -151,7 +151,7 @@ end
 
 $terminal.wrap_at = :auto
 Redwood::start
-index = Redwood::Index.new
+index = Redwood::Index.init
 Redwood::SourceManager.load_sources
 
 say <<EOS
index 992fd0bb74cd0aa7da29729ae38b460d0aad8bc8..8b5bf07f07be990e49ffdb96ff90b623e69a1a9a 100755 (executable)
@@ -21,8 +21,8 @@ No options.
 EOS
 end
 
-index = Redwood::Index.new
-Redwood::SourceManager.new
+index = Redwood::Index.init
+Redwood::SourceManager.init
 index.load
 
 index.each_message :load_spam => true, :load_deleted => true, :load_killed => true do |m|
index db75b11a392f8043346d7af4769d6a6244ec3b2b..43fa5f640f0fcd862ceac622388a471a8573edc2 100755 (executable)
@@ -50,7 +50,7 @@ end.parse(ARGV)
 require "sup"
 Redwood::start
 puts "loading index..."
-index = Redwood::Index.new
+index = Redwood::Index.init
 index.load
 puts "loaded index of #{index.size} messages"
 
index d445bd806f16b6254cbb38fce4a662191514b5a0..2aa00c3720bcd0dbbce60499e5d14a70e01ae418 100755 (executable)
@@ -95,7 +95,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.new
+index = Redwood::Index.init
 
 restored_state = if opts[:restore]
   dump = {}
@@ -112,7 +112,7 @@ else
 end
 
 seen = {}
-index.lock_or_die
+index.lock_interactively or exit
 begin
   index.load
 
index 7635274a90c759fecd2e3a1eb86e9f0c2a035938..6298c97c3e6190e8704ff2cd1333843f09511fea 100755 (executable)
@@ -65,8 +65,8 @@ EOS
 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]
index c9ad8e82d4c945381678a8e2ab1ebb3524cb7356..90f6a57ddb0cbebd037f4a74dd1692a191de522e 100755 (executable)
@@ -58,10 +58,12 @@ add_labels = opts[:add].to_set_of_symbols ","
 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?
+Trollop::die "no sources specified" if ARGV.empty?
 
 Redwood::start
+index = Redwood::Index.init
+index.lock_interactively or exit
 begin
-  index = Redwood::Index.new
   index.load
 
   source_ids = if opts[:all_sources]
index 1afc065b60825e4620fd0926cda7281e4836c6ad..12513f7df7397dd1c9a6a25c507e56f7c5f44ba6 100644 (file)
@@ -119,17 +119,16 @@ module Redwood
   end
 
   def start
-    Redwood::SentManager.new $config[:sent_source] || 'sup://sent'
-    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::SuicideManager.new Redwood::SUICIDE_FN
-    Redwood::CryptoManager.new
-    Redwood::UndoManager.new
-    Redwood::SourceManager.new
+    Redwood::SentManager.init $config[:sent_source] || 'sup://sent'
+    Redwood::ContactManager.init Redwood::CONTACT_FN
+    Redwood::LabelManager.init Redwood::LABEL_FN
+    Redwood::AccountManager.init $config[:accounts]
+    Redwood::DraftManager.init Redwood::DRAFT_DIR
+    Redwood::UpdateManager.init
+    Redwood::PollManager.init
+    Redwood::CryptoManager.init
+    Redwood::UndoManager.init
+    Redwood::SourceManager.init
   end
 
   def finish
@@ -242,33 +241,29 @@ require "sup/hook"
 ## we have to initialize this guy first, because other classes must
 ## reference it in order to register hooks, and they do that at parse
 ## time.
-Redwood::HookManager.new Redwood::HOOK_DIR
+Redwood::HookManager.init Redwood::HOOK_DIR
 
 ## everything we need to get logging working
-require "sup/buffer"
-require "sup/keymap"
-require "sup/mode"
-require "sup/modes/scroll-mode"
-require "sup/modes/text-mode"
-require "sup/modes/log-mode"
 require "sup/logger"
-module Redwood
-  def log s; Logger.log s; end
-  module_function :log
-end
+Redwood::Logger.init.add_sink $stderr
+include Redwood::LogsStuff
 
 ## determine encoding and character set
   $encoding = Locale.current.charset
   if $encoding
-    Redwood::log "using character set encoding #{$encoding.inspect}"
+    debug "using character set encoding #{$encoding.inspect}"
   else
-    Redwood::log "warning: can't find character set by using locale, defaulting to utf-8"
+    warn "can't find character set by using locale, defaulting to utf-8"
     $encoding = "UTF-8"
   end
 
-## now everything else (which can feel free to call Redwood::log at load time)
+require "sup/buffer"
+require "sup/keymap"
+require "sup/mode"
+require "sup/modes/scroll-mode"
+require "sup/modes/text-mode"
+require "sup/modes/log-mode"
 require "sup/update"
-require "sup/suicide"
 require "sup/message-chunks"
 require "sup/message"
 require "sup/source"
@@ -278,6 +273,7 @@ require "sup/imap"
 require "sup/person"
 require "sup/account"
 require "sup/thread"
+require "sup/interactive-lock"
 require "sup/index"
 require "sup/textfield"
 require "sup/colormap"
@@ -308,6 +304,7 @@ require "sup/modes/buffer-list-mode"
 require "sup/modes/poll-mode"
 require "sup/modes/file-browser-mode"
 require "sup/modes/completion-mode"
+require "sup/modes/console-mode"
 require "sup/sent"
 
 $:.each do |base|
index 6f86129ca5d8997b0572f38512ba84a691ee4a7c..eed2794d5f8562647a52f18686f2f635963d5799 100644 (file)
@@ -25,8 +25,6 @@ class AccountManager
 
     add_account accounts[:default], true
     accounts.each { |k, v| add_account v, false unless k == :default }
-
-    self.class.i_am_the_instance self
   end
 
   def user_accounts; @accounts.keys; end
index 3bd3fe8e96a1563ff3ae4fa748b80203a1320cb8..12dd11949b90797753e8fefc4037ffad52f5a95f 100644 (file)
@@ -28,10 +28,12 @@ module Ncurses
   ## magically, this stuff seems to work now. i could swear it didn't
   ## before. hm.
   def nonblocking_getch
-    if IO.select([$stdin], nil, nil, 1)
-      Ncurses.getch
-    else
-      nil
+    ## INSANTIY
+    ## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
+    ## background threads will be BLOCKED. (except in very modern versions
+    ## of libncurses-ruby. the current one on ubuntu seems to work well.)
+    if IO.select([$stdin], nil, nil, 0.5)
+      c = Ncurses.getch
     end
   end
 
@@ -196,10 +198,13 @@ EOS
     @flash = nil
     @shelled = @asking = false
     @in_x = ENV["TERM"] =~ /(xterm|rxvt|screen)/
-
-    self.class.i_am_the_instance self
+    @sigwinch_happened = false
+    @sigwinch_mutex = Mutex.new
   end
 
+  def sigwinch_happened!; @sigwinch_mutex.synchronize { @sigwinch_happened = true } end
+  def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end
+
   def buffers; @name_map.to_a; end
 
   def focus_on buf
@@ -230,14 +235,20 @@ EOS
   ## have to change this. but it's not clear that we will ever actually
   ## do that.
   def roll_buffers
-    @buffers.last.force_to_top = false
-    raise_to_front @buffers.first
+    bufs = rollable_buffers
+    bufs.last.force_to_top = false
+    raise_to_front bufs.first
   end
 
   def roll_buffers_backwards
-    return unless @buffers.length > 1
-    @buffers.last.force_to_top = false
-    raise_to_front @buffers[@buffers.length - 2]
+    bufs = rollable_buffers
+    return unless bufs.length > 1
+    bufs.last.force_to_top = false
+    raise_to_front bufs[bufs.length - 2]
+  end
+
+  def rollable_buffers
+    @buffers.select { |b| !b.system? || @buffers.last == b }
   end
 
   def handle_input c
@@ -261,6 +272,14 @@ EOS
   def completely_redraw_screen
     return if @shelled
 
+    ## this magic makes Ncurses get the new size of the screen
+    Ncurses.endwin
+    Ncurses.stdscr.keypad 1
+    Ncurses.curs_set 0
+    Ncurses.refresh
+    @sigwinch_mutex.synchronize { @sigwinch_happened = false }
+    debug "new screen size is #{Ncurses.rows} x #{Ncurses.cols}"
+
     status, title = get_status_and_title(@focus_buf) # must be called outside of the ncurses lock
 
     Ncurses.sync do
index 38787cddc41374a9f9adf583160627a5dfb448e0..fbbbfc9e9405d1be3eb88d4dd647b6e96e9e85d8 100644 (file)
@@ -129,7 +129,7 @@ class Colormap
       @next_id = (@next_id + 1) % NUM_COLORS
       @next_id += 1 if @next_id == 0 # 0 is always white on black
       id = @next_id
-      Redwood::log "colormap: for color #{sym}, using id #{id} -> #{fg}, #{bg}"
+      debug "colormap: for color #{sym}, using id #{id} -> #{fg}, #{bg}"
       Curses.init_pair id, fg, bg or raise ArgumentError,
         "couldn't initialize curses color pair #{fg}, #{bg} (key #{id})"
 
@@ -137,7 +137,7 @@ class Colormap
       ## delete the old mapping, if it exists
       if @users[cp]
         @users[cp].each do |usym|
-          Redwood::log "dropping color #{usym} (#{id})"
+          warn "dropping color #{usym} (#{id})"
           @entries[usym][3] = nil
         end
         @users[cp] = []
@@ -155,7 +155,7 @@ class Colormap
   ## to the default ones.
   def populate_colormap
     user_colors = if File.exists? Redwood::COLOR_FN
-      Redwood::log "loading user colors from #{Redwood::COLOR_FN}"
+      debug "loading user colors from #{Redwood::COLOR_FN}"
       Redwood::load_yaml_obj Redwood::COLOR_FN
     end
 
@@ -171,7 +171,7 @@ class Colormap
             fg = Curses.const_get "COLOR_#{ufg.upcase}"
           rescue NameError
             error ||= "Warning: there is no color named \"#{ufg}\", using fallback."
-            Redwood::log "Warning: there is no color named \"#{ufg}\""
+            warn "there is no color named \"#{ufg}\""
           end
         end
 
@@ -180,7 +180,7 @@ class Colormap
             bg = Curses.const_get "COLOR_#{ubg.upcase}"
           rescue NameError
             error ||= "Warning: there is no color named \"#{ubg}\", using fallback."
-            Redwood::log "Warning: there is no color named \"#{ubg}\""
+            warn "there is no color named \"#{ubg}\""
           end
         end
 
@@ -190,7 +190,7 @@ class Colormap
               Curses.const_get "A_#{a.upcase}"
             rescue NameError
               error ||= "Warning: there is no attribute named \"#{a}\", using fallback."
-              Redwood::log "Warning: there is no attribute named \"#{a}\", using fallback."
+              warn "there is no attribute named \"#{a}\", using fallback."
             end
           end
         end
index 51fd0e978c902f17485c8fcd81f20cc766b74157..c489aaff5a4d67ce9d7156a10013f329bf790f11 100644 (file)
@@ -22,8 +22,6 @@ class ContactManager
         @a2p[aalias] = p unless aalias.nil? || aalias.empty?
       end
     end
-
-    self.class.i_am_the_instance self
   end
 
   def contacts; @p2a.keys end
index f68009d91bd7b5e42ce6216e4a3bad12335d03f6..7f044b99428d0beb80bfb22fa1f4cda6bc127011 100644 (file)
@@ -13,17 +13,15 @@ class CryptoManager
 
   def initialize
     @mutex = Mutex.new
-    self.class.i_am_the_instance self
 
     bin = `which gpg`.chomp
-
     @cmd =
       case bin
       when /\S/
-        Redwood::log "crypto: detected gpg binary in #{bin}"
+        debug "crypto: detected gpg binary in #{bin}"
         "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent"
       else
-        Redwood::log "crypto: no gpg binary detected"
+        debug "crypto: no gpg binary detected"
         nil
       end
   end
@@ -156,9 +154,7 @@ private
 
   def run_gpg args
     cmd = "#{@cmd} #{args} 2> /dev/null"
-    #Redwood::log "crypto: running: #{cmd}"
     output = `#{cmd}`
-    #Redwood::log "crypto: output: #{output.inspect}" unless $?.success?
     output
   end
 end
index ce8f064aca662df995dc2a21ef2295c4f76fdb73..5ea2935f2180f3cb8a94ac996a50ce97dcc6d1ac 100644 (file)
@@ -7,7 +7,6 @@ class DraftManager
   def initialize dir
     @dir = dir
     @source = nil
-    self.class.i_am_the_instance self
   end
 
   def self.source_name; "sup://drafts"; end
index 3655d7a1d3d4d00765d57620ac65feecf9c5e458..2de8727f2e2652577d0ed4e8634caae8030003ae 100644 (file)
@@ -4,6 +4,16 @@ module Redwood
 
 class FerretIndex < BaseIndex
 
+  HookManager.register "custom-search", <<EOS
+Executes before a string search is applied to the index,
+returning a new search string.
+Variables:
+  subs: The string being searched. Be careful about shadowing:
+    this variable is actually a method, so use a temporary variable
+    or explicitly call self.subs; the substitutions in index.rb
+    don't actually work.
+EOS
+
   def initialize dir=BASE_DIR
     super
 
@@ -18,13 +28,13 @@ class FerretIndex < BaseIndex
 
   def load_index dir=File.join(@dir, "ferret")
     if File.exists? dir
-      Redwood::log "loading index..."
+      debug "loading index..."
       @index_mutex.synchronize do
         @index = Ferret::Index::Index.new(:path => dir, :analyzer => @analyzer, :id_field => 'message_id')
-        Redwood::log "loaded index of #{@index.size} messages"
+        debug "loaded index of #{@index.size} messages"
       end
     else
-      Redwood::log "creating index..."
+      debug "creating index..."
       @index_mutex.synchronize do
         field_infos = Ferret::Index::FieldInfos.new :store => :yes
         field_infos.add_field :message_id, :index => :untokenized
@@ -90,9 +100,9 @@ class FerretIndex < BaseIndex
     if entry[:source_id] && entry[:source_info] && entry[:label] &&
       ((entry[:source_id].to_i > source_id) || (entry[:source_info].to_i < m.source_info))
       labels += entry[:label].to_set_of_symbols
-      #Redwood::log "found updated version of message #{m.id}: #{m.subj}"
-      #Redwood::log "previous version was at #{entry[:source_id].inspect}:#{entry[:source_info].inspect}, this version at #{source_id.inspect}:#{m.source_info.inspect}"
-      #Redwood::log "merged labels are #{labels.inspect} (index #{entry[:label].inspect}, message #{m.labels.inspect})"
+      #debug "found updated version of message #{m.id}: #{m.subj}"
+      #debug "previous version was at #{entry[:source_id].inspect}:#{entry[:source_info].inspect}, this version at #{source_id.inspect}:#{m.source_info.inspect}"
+      #debug "merged labels are #{labels.inspect} (index #{entry[:label].inspect}, message #{m.labels.inspect})"
       entry = {}
     end
 
@@ -151,7 +161,7 @@ class FerretIndex < BaseIndex
     while true
       limit = (query[:limit])? [EACH_BY_DATE_NUM, query[:limit] - offset].min : EACH_BY_DATE_NUM
       results = @index_mutex.synchronize { @index.search ferret_query, :sort => "date DESC", :limit => limit, :offset => offset }
-      Redwood::log "got #{results.total_hits} results for query (offset #{offset}) #{ferret_query.inspect}"
+      debug "got #{results.total_hits} results for query (offset #{offset}) #{ferret_query.inspect}"
       results.hits.each do |hit|
         yield @index_mutex.synchronize { @index[hit.doc][:message_id] }, lambda { build_message hit.doc }
       end
@@ -170,7 +180,7 @@ class FerretIndex < BaseIndex
   SAME_SUBJECT_DATE_LIMIT = 7
   MAX_CLAUSES = 1000
   def each_message_in_thread_for m, opts={}
-    #Redwood::log "Building thread for #{m.id}: #{m.subj}"
+    #debug "Building thread for #{m.id}: #{m.subj}"
     messages = {}
     searched = {}
     num_queries = 0
@@ -191,10 +201,10 @@ class FerretIndex < BaseIndex
       q = build_ferret_query :qobj => q
 
       p1 = @index_mutex.synchronize { @index.search(q).hits.map { |hit| @index[hit.doc][:message_id] } }
-      Redwood::log "found #{p1.size} results for subject query #{q}"
+      debug "found #{p1.size} results for subject query #{q}"
 
       p2 = @index_mutex.synchronize { @index.search(q.to_s, :limit => :all).hits.map { |hit| @index[hit.doc][:message_id] } }
-      Redwood::log "found #{p2.size} results in string form"
+      debug "found #{p2.size} results in string form"
 
       pending = (pending + p1 + p2).uniq
     end
@@ -225,7 +235,7 @@ class FerretIndex < BaseIndex
           end
           mid = @index[docid][:message_id]
           unless messages.member?(mid)
-            #Redwood::log "got #{mid} as a child of #{id}"
+            #debug "got #{mid} as a child of #{id}"
             messages[mid] ||= lambda { build_message docid }
             refs = @index[docid][:refs].split
             pending += refs.select { |id| !searched[id] }
@@ -235,10 +245,10 @@ class FerretIndex < BaseIndex
     end
 
     if killed
-      #Redwood::log "thread for #{m.id} is killed, ignoring"
+      #debug "thread for #{m.id} is killed, ignoring"
       false
     else
-      #Redwood::log "ran #{num_queries} queries to build thread of #{messages.size} messages for #{m.id}: #{m.subj}" if num_queries > 0
+      #debug "ran #{num_queries} queries to build thread of #{messages.size} messages for #{m.id}: #{m.subj}" if num_queries > 0
       messages.each { |mid, builder| yield mid, builder }
       true
     end
@@ -285,13 +295,13 @@ class FerretIndex < BaseIndex
     end
     q.add_query Ferret::Search::TermQuery.new(:label, "spam"), :must_not
 
-    Redwood::log "contact search: #{q}"
+    debug "contact search: #{q}"
     contacts = {}
     num = h[:num] || 20
     @index_mutex.synchronize do
       @index.search_each q, :sort => "date DESC", :limit => :all do |docid, score|
         break if contacts.size >= num
-        #Redwood::log "got message #{docid} to: #{@index[docid][:to].inspect} and from: #{@index[docid][:from].inspect}"
+        #debug "got message #{docid} to: #{@index[docid][:to].inspect} and from: #{@index[docid][:from].inspect}"
         f = @index[docid][:from]
         t = @index[docid][:to]
 
@@ -332,7 +342,9 @@ class FerretIndex < BaseIndex
   def parse_query s
     query = {}
 
-    subs = s.gsub(/\b(to|from):(\S+)\b/) do
+    subs = HookManager.run("custom-search", :subs => s) || s
+
+    subs = subs.gsub(/\b(to|from):(\S+)\b/) do
       field, name = $1, $2
       if(p = ContactManager.contact_for(name))
         [field, p.email]
@@ -381,10 +393,10 @@ class FerretIndex < BaseIndex
       field, name = $1, ($3 || $4)
       case field
       when "filename"
-        Redwood::log "filename - translated #{field}:#{name} to attachments:(#{name.downcase})"
+        debug "filename: translated #{field}:#{name} to attachments:(#{name.downcase})"
         "attachments:(#{name.downcase})"
       when "filetype"
-        Redwood::log "filetype - translated #{field}:#{name} to attachments:(*.#{name.downcase})"
+        debug "filetype: translated #{field}:#{name} to attachments:(*.#{name.downcase})"
         "attachments:(*.#{name.downcase})"
       end
     end
@@ -396,13 +408,13 @@ class FerretIndex < BaseIndex
         if realdate
           case field
           when "after"
-            Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate.end}"
+            debug "chronic: translated #{field}:#{datestr} to #{realdate.end}"
             "date:(>= #{sprintf "%012d", realdate.end.to_i})"
           when "before"
-            Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
+            debug "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
             "date:(<= #{sprintf "%012d", realdate.begin.to_i})"
           else
-            Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate}"
+            debug "chronic: translated #{field}:#{datestr} to #{realdate}"
             "date:(<= #{sprintf "%012d", realdate.end.to_i}) date:(>= #{sprintf "%012d", realdate.begin.to_i})"
           end
         else
index 0a0a2f672fca775e4bb792896537d2ec2535fac3..9c48bcabea67caa6dc011c030213f0cc6b94026e 100644 (file)
@@ -19,6 +19,11 @@ class HookManager
 
     attr_writer :__locals
 
+    ## an annoying gotcha here is that if you try something
+    ## like var = var.foo(), var will magically get allocated
+    ## to Nil and method_missing will never get called.  You
+    ## can work around this by calling self.var or simply
+    ## not assigning it to itself.
     def method_missing m, *a
       case @__locals[m]
       when Proc
@@ -40,7 +45,7 @@ class HookManager
     end
 
     def log s
-      Redwood::log "hook[#@__name]: #{s}"
+      info "hook[#@__name]: #{s}"
     end
 
     def ask_yes_or_no q
@@ -79,8 +84,6 @@ class HookManager
     @tags = {}
 
     Dir.mkdir dir unless File.exists? dir
-
-    self.class.i_am_the_instance self
   end
 
   attr_reader :tags
@@ -125,6 +128,8 @@ EOS
 
   def enabled? name; !hook_for(name).nil? end
 
+  def clear; @hooks.clear; end
+
 private
 
   def hook_for name
@@ -148,7 +153,7 @@ private
   end
 
   def log m
-    Redwood::log("hook: " + m)
+    info("hook: " + m)
   end
 end
 
index b048ba9ea18f9a2e538b1df698db03169e25f0a8..3cf6489c59ad62d6dc6cae83cb2ad0dae419062a 100644 (file)
@@ -161,13 +161,13 @@ class IMAP < Source
     return if last_id == @ids.length
 
     range = (@ids.length + 1) .. last_id
-    Redwood::log "fetching IMAP headers #{range}"
+    debug "fetching IMAP headers #{range}"
     fetch(range, ['RFC822.SIZE', 'INTERNALDATE', 'FLAGS']).each do |v|
       id = make_id v
       @ids << id
       @imap_state[id] = { :id => v.seqno, :flags => v.attr["FLAGS"] }
     end
-    Redwood::log "done fetching IMAP headers"
+    debug "done fetching IMAP headers"
   end
   synchronized :scan_mailbox
 
@@ -226,7 +226,7 @@ private
     if good_results.empty?
       raise FatalSourceError, "no IMAP response for #{ids} containing all fields #{fields.join(', ')} (got #{results.size} results)"
     elsif good_results.size < results.size
-      Redwood::log "Your IMAP server sucks. It sent #{results.size} results for a request for #{good_results.size} messages. What are you using, Binc?"
+      warn "Your IMAP server sucks. It sent #{results.size} results for a request for #{good_results.size} messages. What are you using, Binc?"
     end
 
     good_results
@@ -254,12 +254,12 @@ private
           raise Net::IMAP::NoResponseError unless @imap.capability().member? "AUTH=CRAM-MD5"
           @imap.authenticate 'CRAM-MD5', @username, @password
         rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
-          Redwood::log "CRAM-MD5 authentication failed: #{e.class}. Trying LOGIN auth..."
+          debug "CRAM-MD5 authentication failed: #{e.class}. Trying LOGIN auth..."
           begin
             raise Net::IMAP::NoResponseError unless @imap.capability().member? "AUTH=LOGIN"
             @imap.authenticate 'LOGIN', @username, @password
           rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
-            Redwood::log "LOGIN authentication failed: #{e.class}. Trying plain-text LOGIN..."
+            debug "LOGIN authentication failed: #{e.class}. Trying plain-text LOGIN..."
             @imap.login @username, @password
           end
         end
@@ -276,7 +276,7 @@ private
 
   def say s
     @say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
-    Redwood::log s
+    debug s
   end
 
   def shutup
@@ -333,7 +333,7 @@ private
       rescue *RECOVERABLE_ERRORS => e
         if (retries += 1) <= 3
           @imap = nil
-          Redwood::log "got #{e.class.name}: #{e.message.inspect}"
+          warn "got #{e.class.name}: #{e.message.inspect}"
           sleep 2
           retry
         end
index 122026a4a1a7ca4e6a20663b71fe76317075c8a9..ff03f195f6bca74aefdb23eedecc01c2172ee39c 100644 (file)
@@ -6,13 +6,15 @@ begin
   require 'chronic'
   $have_chronic = true
 rescue LoadError => e
-  Redwood::log "optional 'chronic' library not found (run 'gem install chronic' to install)"
+  debug "optional 'chronic' library not found; date-time query restrictions disabled"
   $have_chronic = false
 end
 
 module Redwood
 
 class BaseIndex
+  include InteractiveLock
+
   class LockError < StandardError
     def initialize h
       @h = h
@@ -26,13 +28,12 @@ class BaseIndex
   def initialize dir=BASE_DIR
     @dir = dir
     @lock = Lockfile.new lockfile, :retries => 0, :max_age => nil
-    self.class.i_am_the_instance self
   end
 
   def lockfile; File.join @dir, "lock" end
 
   def lock
-    Redwood::log "locking #{lockfile}..."
+    debug "locking #{lockfile}..."
     begin
       @lock.lock
     rescue Lockfile::MaxTriesLockError
@@ -54,45 +55,9 @@ class BaseIndex
     @lock_update_thread = nil
   end
 
-  def possibly_pluralize number_of, kind
-    "#{number_of} #{kind}" +
-        if number_of == 1 then "" else "s" end
-  end
-
-  def fancy_lock_error_message_for e
-    secs = (Time.now - e.mtime).to_i
-    mins = secs / 60
-    time =
-      if mins == 0
-        possibly_pluralize secs , "second"
-      else
-        possibly_pluralize mins, "minute"
-      end
-
-    <<EOS
-Error: the sup index is locked by another process! User '#{e.user}' on
-host '#{e.host}' is running #{e.pname} with pid #{e.pid}. The process was alive
-as of #{time} ago.
-EOS
-  end
-
-  def lock_or_die
-    begin
-      lock
-    rescue LockError => e
-      $stderr.puts fancy_lock_error_message_for(e)
-      $stderr.puts <<EOS
-
-You can wait for the process to finish, or, if it crashed and left a
-stale lock file behind, you can manually delete #{@lock.path}.
-EOS
-      exit
-    end
-  end
-
   def unlock
     if @lock && @lock.locked?
-      Redwood::log "unlocking #{lockfile}..."
+      debug "unlocking #{lockfile}..."
       @lock.unlock
     end
   end
@@ -103,7 +68,7 @@ EOS
   end
 
   def save
-    Redwood::log "saving index and sources..."
+    debug "saving index and sources..."
     FileUtils.mkdir_p @dir unless File.exists? @dir
     SourceManager.save_sources
     save_index
@@ -216,6 +181,6 @@ case index_name
   else fail "unknown index type #{index_name.inspect}"
 end
 Index = Redwood.const_get "#{index_name.capitalize}Index"
-Redwood::log "using index #{Index.name}"
+debug "using index #{Index.name}"
 
 end
diff --git a/lib/sup/interactive-lock.rb b/lib/sup/interactive-lock.rb
new file mode 100644 (file)
index 0000000..92a5ead
--- /dev/null
@@ -0,0 +1,74 @@
+require 'fileutils'
+
+module Redwood
+
+## wrap a nice interactive layer on top of anything that has a #lock method
+## which throws a LockError which responds to #user, #host, #mtim, #pname, and
+## #pid.
+
+module InteractiveLock
+  def pluralize number_of, kind; "#{number_of} #{kind}" + (number_of == 1 ? "" : "s") end
+
+  def time_ago_in_words time
+    secs = (Time.now - time).to_i
+    mins = secs / 60
+    time = if mins == 0
+      pluralize secs, "second"
+    else
+      pluralize mins, "minute"
+    end
+  end
+
+  DELAY = 5 # seconds
+
+  def lock_interactively stream=$stderr
+    begin
+      Index.lock
+    rescue Index::LockError => e
+      stream.puts <<EOS
+Error: the index is locked by another process! User '#{e.user}' on
+host '#{e.host}' is running #{e.pname} with pid #{e.pid}.
+The process was alive as of at least #{time_ago_in_words e.mtime} ago.
+
+EOS
+      stream.print "Should I ask that process to kill itself (y/n)? "
+      stream.flush
+
+      success = if $stdin.gets =~ /^\s*y(es)?\s*$/i
+        stream.puts "Ok, trying to kill process..."
+
+        begin
+          Process.kill "TERM", e.pid.to_i
+          sleep DELAY
+        rescue Errno::ESRCH # no such process
+          stream.puts "Hm, I couldn't kill it."
+        end
+
+        stream.puts "Let's try that again."
+        begin
+          Index.lock
+        rescue Index::LockError => e
+          stream.puts "I couldn't lock the index. The lockfile might just be stale."
+          stream.print "Should I just remove it and continue? (y/n) "
+          stream.flush
+
+          if $stdin.gets =~ /^\s*y(es)?\s*$/i
+            FileUtils.rm e.path
+
+            stream.puts "Let's try that one more time."
+            begin
+              Index.lock
+              true
+            rescue Index::LockError => e
+            end
+          end
+        end
+      end
+
+      stream.puts "Sorry, couldn't unlock the index." unless success
+      success
+    end
+  end
+end
+
+end
index 3e7bacbd795b2c1b9a3d49754d6f41878608f57a..67474c2b79ed3393aa3321e458fdb4675225dfe1 100644 (file)
@@ -22,8 +22,6 @@ class LabelManager
     @new_labels = {}
     @modified = false
     labels.each { |t| @labels[t] = true }
-
-    self.class.i_am_the_instance self
   end
 
   def new_label? l; @new_labels.include?(l) end
index 4ac6551bff21e50ab45704f295d6d930edf1ce6d..ccaeae0c0728f15c08898a948f70b64b547f708b 100644 (file)
@@ -1,54 +1,73 @@
+require "sup"
+require 'stringio'
+require 'thread'
+
 module Redwood
 
+## simple centralized logger. outputs to multiple sinks by calling << on them.
+## also keeps a record of all messages, so that adding a new sink will send all
+## previous messages to it by default.
 class Logger
-  @@instance = nil
+  include Singleton
 
-  attr_reader :buf
+  LEVELS = %w(debug info warn error) # in order!
 
-  def initialize
-    raise "only one Log can be defined" if @@instance
-    @@instance = self
-    @mode = LogMode.new
-    @respawn = true
-    @spawning = false # to prevent infinite loops!
+  def initialize level=nil
+    level ||= ENV["SUP_LOG_LEVEL"] || "info"
+    @level = LEVELS.index(level) or raise ArgumentError, "invalid log level #{level.inspect}: should be one of #{LEVELS * ', '}"
+    @mutex = Mutex.new
+    @buf = StringIO.new
+    @sinks = []
   end
 
-  ## must be called if you want to see anything!
-  ## once called, will respawn if killed...
-  def make_buf
-    return if @mode.buffer || !BufferManager.instantiated? || !@respawn || @spawning
-    @spawning = true
-    @mode.buffer = BufferManager.instance.spawn "log", @mode, :hidden => true, :system => true
-    @spawning = false
+  def level; LEVELS[@level] end
+
+  def add_sink s, copy_current=true
+    @mutex.synchronize do
+      @sinks << s
+      s << @buf.string if copy_current
+    end
   end
 
-  def log s
-#    $stderr.puts s
-    make_buf
-    prefix = "#{Time.now}: "
-    padding = " " * prefix.length
-    first = true
-    s.split(/[\r\n]/).each do |l|
-      l = l.chomp
-      if first
-        first = false
-        @mode << "#{prefix}#{l}\n"
-      else
-        @mode << "#{padding}#{l}\n"
+  def remove_sink s; @mutex.synchronize { @sinks.delete s } end
+  def remove_all_sinks!; @mutex.synchronize { @sinks.clear } end
+  def clear!; @mutex.synchronize { @buf = StringIO.new } end
+
+  LEVELS.each_with_index do |l, method_level|
+    define_method(l) do |s|
+      if method_level >= @level
+        send_message format_message(l, Time.now, s)
       end
     end
-    $stderr.puts "[#{Time.now}] #{s.chomp}" unless BufferManager.instantiated? && @mode.buffer
   end
-  
-  def self.method_missing m, *a
-    @@instance = Logger.new unless @@instance
-    @@instance.send m, *a
+
+  ## send a message regardless of the current logging level
+  def force_message m; send_message format_message(nil, Time.now, m) end
+
+private
+
+  ## level can be nil!
+  def format_message level, time, msg
+    prefix = case level
+      when "warn"; "WARNING: "
+      when "error"; "ERROR: "
+      else ""
+    end
+    "[#{time.to_s}] #{prefix}#{msg}\n"
   end
 
-  def self.buffer
-    @@instance.buf
+  ## actually distribute the message
+  def send_message m
+    @mutex.synchronize do
+      @sinks.each { |sink| sink << m }
+      @buf << m
+    end
   end
 end
 
+## include me to have top-level #debug, #info, etc. methods.
+module LogsStuff
+  Logger::LEVELS.each { |l| define_method(l) { |s| Logger.instance.send(l, s) } }
 end
 
+end
index db35dad0f52bab455eb47e2ef027eb63cf8d54b1..2c33e3bdc06b08f68b1d4b3caf564021eb6d71b8 100644 (file)
@@ -116,7 +116,7 @@ class Maildir < Source
 
     initial_poll = @ids.empty?
 
-    Redwood::log "scanning maildir #@dir..."
+    debug "scanning maildir #@dir..."
     begin
       @mtimes.each_key do |d|
        subdir = File.join(@dir, d)
@@ -135,7 +135,7 @@ class Maildir < Source
            @ids_to_fns[id] = fn
          end
        else
-         Redwood::log "no poll on #{d}.  mtime on indicates no new messages."
+         debug "no poll on #{d}.  mtime on indicates no new messages."
        end
       end
       @ids = @dir_ids.values.flatten.uniq.sort!
@@ -143,7 +143,7 @@ class Maildir < Source
       raise FatalSourceError, "Problem scanning Maildir directories: #{e.message}."
     end
     
-    Redwood::log "done scanning maildir"
+    debug "done scanning maildir"
     @last_scan = Time.now
   end
   synchronized :scan_mailbox
@@ -213,7 +213,7 @@ private
 
   def maildir_data msg
     fn = File.basename @ids_to_fns[msg]
-    fn =~ %r{^([^:,]+):([12]),([DFPRST]*)$}
+    fn =~ %r{^([^:]+):([12]),([DFPRST]*)$}
     [($1 || fn), ($2 || "2"), ($3 || "")]
   end
 
index e1e3a4d1aed75c0a3d190cdc9ce8257ff46a56e4..3f3abadefeb6f3833970abf027e6e8f77ce7dd45 100644 (file)
@@ -15,7 +15,7 @@ module MBox
       Time.parse time, 0
       true
     rescue NoMethodError
-      Redwood::log "found invalid date in potential mbox split line, not splitting: #{l.inspect}"
+      warn "found invalid date in potential mbox split line, not splitting: #{l.inspect}"
       false
     end
   end
index d47463692e07bedce205e4c3527f87f345c528c6..4ae4bbadf6f875b8655dbe5b461bca2b31afef22 100644 (file)
@@ -16,12 +16,6 @@ class SSHFileError < StandardError; end
 ## all of the methods here can throw SSHFileErrors, SocketErrors,
 ## Net::SSH::Exceptions and Errno::ENOENTs.
 
-## debugging TODO: remove me
-def debug s
-  Redwood::log s
-end
-module_function :debug
-
 ## a simple buffer of contiguous data
 class Buffer
   def initialize
@@ -154,7 +148,7 @@ private
   ## TODO: share this code with imap
   def say s
     @say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
-    Redwood::log s
+    info s
   end
 
   def shutup
index 0d742d99e746a9ce96a4c11eddcd57506fcae22f..ce7d1ee4f5fde1e1df37d1ed5c6223e6ba5b3481 100644 (file)
@@ -131,9 +131,9 @@ EOS
     def initial_state; :open end
     def viewable?; @lines.nil? end
     def view_default! path
-      cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}' 2>/dev/null"
-      Redwood::log "running: #{cmd.inspect}"
-      system cmd
+      cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'"
+      debug "running: #{cmd.inspect}"
+      BufferManager.shell_out(cmd)
       $? == 0
     end
 
@@ -208,13 +208,25 @@ EOS
 
   class EnclosedMessage
     attr_reader :lines
-    def initialize from, body
-      @from = from
-      @lines = body.split "\n"
-    end
+    def initialize from, to, cc, date, subj
+      @from = from ? "unknown sender" : from.full_adress
+      @to = to ? "" : to.map { |p| p.full_address }.join(", ")
+      @cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
+      if date
+        @date = date.rfc822
+      else
+        @date = ""
+      end
 
-    def from
-      @from ? @from.longname : "unknown sender"
+      @subj = subj
+
+      @lines = "\nFrom: #{from}\n"
+      @lines += "To: #{to}\n"
+      if !cc.empty?
+        @lines += "Cc: #{cc}\n"
+      end
+      @lines += "Date: #{date}\n"
+      @lines += "Subject: #{subj}\n\n"
     end
 
     def inlineable?; false end
@@ -224,7 +236,7 @@ EOS
     def viewable?; false end
 
     def patina_color; :generic_notice_patina_color end
-    def patina_text; "Begin enclosed message from #{from} (#{@lines.length} lines)" end
+    def patina_text; "Begin enclosed message sent on #{@date}" end
 
     def color; :quote_color end
   end
index 1e9c659c9a28930ec3567c9880e61c2ebf127669..965c10e53ac257748cf3a2466d79c9431783b3bc 100644 (file)
@@ -73,7 +73,7 @@ class Message
     else
       id = "sup-faked-" + Digest::MD5.hexdigest(raw_header)
       from = header["from"]
-      #Redwood::log "faking non-existent message-id for message from #{from}: #{id}"
+      #debug "faking non-existent message-id for message from #{from}: #{id}"
       id
     end
 
@@ -81,7 +81,7 @@ class Message
       header["from"]
     else
       name = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
-      #Redwood::log "faking non-existent sender for message #@id: #{name}"
+      #debug "faking non-existent sender for message #@id: #{name}"
       name
     end)
 
@@ -92,11 +92,11 @@ class Message
       begin
         Time.parse date
       rescue ArgumentError => e
-        #Redwood::log "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
+        #debug "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
         Time.now
       end
     else
-      #Redwood::log "faking non-existent date header for #{@id}"
+      #debug "faking non-existent date header for #{@id}"
       Time.now
     end
 
@@ -210,7 +210,7 @@ class Message
           parse_header @source.load_header(@source_info)
           message_to_chunks @source.load_message(@source_info)
         rescue SourceError, SocketError => e
-          Redwood::log "problem getting messages from #{@source}: #{e.message}"
+          warn "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
@@ -244,7 +244,7 @@ EOS
     begin
       yield
     rescue SourceError => e
-      Redwood::log "problem getting messages from #{@source}: #{e.message}"
+      warn "problem getting messages from #{@source}: #{e.message}"
       @source.error ||= e
       Redwood::report_broken_sources :force_to_top => true
       error_message e.message
@@ -335,25 +335,25 @@ private
 
   def multipart_signed_to_chunks m
     if m.body.size != 2
-      Redwood::log "warning: multipart/signed with #{m.body.size} parts (expecting 2)"
+      warn "multipart/signed with #{m.body.size} parts (expecting 2)"
       return
     end
 
     payload, signature = m.body
     if signature.multipart?
-      Redwood::log "warning: multipart/signed with payload multipart #{payload.multipart?} and signature multipart #{signature.multipart?}"
+      warn "multipart/signed with payload multipart #{payload.multipart?} and signature multipart #{signature.multipart?}"
       return
     end
 
     ## this probably will never happen
     if payload.header.content_type == "application/pgp-signature"
-      Redwood::log "warning: multipart/signed with payload content type #{payload.header.content_type}"
+      warn "multipart/signed with payload content type #{payload.header.content_type}"
       return
     end
 
     if signature.header.content_type != "application/pgp-signature"
       ## unknown signature type; just ignore.
-      #Redwood::log "warning: multipart/signed with signature content type #{signature.header.content_type}"
+      #warn "multipart/signed with signature content type #{signature.header.content_type}"
       return
     end
 
@@ -362,23 +362,23 @@ private
 
   def multipart_encrypted_to_chunks m
     if m.body.size != 2
-      Redwood::log "warning: multipart/encrypted with #{m.body.size} parts (expecting 2)"
+      warn "multipart/encrypted with #{m.body.size} parts (expecting 2)"
       return
     end
 
     control, payload = m.body
     if control.multipart?
-      Redwood::log "warning: multipart/encrypted with control multipart #{control.multipart?} and payload multipart #{payload.multipart?}"
+      warn "multipart/encrypted with control multipart #{control.multipart?} and payload multipart #{payload.multipart?}"
       return
     end
 
     if payload.header.content_type != "application/octet-stream"
-      Redwood::log "warning: multipart/encrypted with payload content type #{payload.header.content_type}"
+      warn "multipart/encrypted with payload content type #{payload.header.content_type}"
       return
     end
 
     if control.header.content_type != "application/pgp-encrypted"
-      Redwood::log "warning: multipart/encrypted with control content type #{signature.header.content_type}"
+      warn "multipart/encrypted with control content type #{signature.header.content_type}"
       return
     end
 
@@ -406,10 +406,19 @@ private
       chunks
     elsif m.header.content_type == "message/rfc822"
       payload = RMail::Parser.read(m.body)
-      from = payload.header.from.first
-      from_person = from ? Person.from_address(from.format) : nil
-      [Chunk::EnclosedMessage.new(from_person, payload.to_s)] +
-        message_to_chunks(payload, encrypted)
+      from = payload.header.from.first ? payload.header.from.first.format : ""
+      to = payload.header.to.map { |p| p.format }.join(", ")
+      cc = payload.header.cc.map { |p| p.format }.join(", ")
+      subj = payload.header.subject
+      subj = subj ? Message.normalize_subj(payload.header.subject.gsub(/\s+/, " ").gsub(/\s+$/, "")) : subj
+      if Rfc2047.is_encoded? subj
+        subj = Rfc2047.decode_to $encoding, subj
+      end
+      msgdate = payload.header.date
+      from_person = from ? Person.from_address(from) : nil
+      to_people = to ? Person.from_address_list(to) : nil
+      cc_people = cc ? Person.from_address_list(cc) : nil
+      [Chunk::EnclosedMessage.new(from_person, to_people, cc_people, msgdate, subj)] + message_to_chunks(payload, encrypted)
     else
       filename =
         ## first, paw through the headers looking for a filename
index 6433492c4d50acb187c1ae426620fde6a13b36be..209ca45e44bb36333159627335bd64dd7f8b50ea 100644 (file)
@@ -92,10 +92,10 @@ EOS
       unless err.empty?
         message = err.first.read
         if message =~ /^\s*$/
-          Redwood::log "error running #{command} (but no error message)"
+          warn "error running #{command} (but no error message)"
           BufferManager.flash "Error running #{command}!"
         else
-          Redwood::log "error running #{command}: #{message}"
+          warn "error running #{command}: #{message}"
           BufferManager.flash "Error: #{message}"
         end
         return
diff --git a/lib/sup/modes/console-mode.rb b/lib/sup/modes/console-mode.rb
new file mode 100644 (file)
index 0000000..af3d66d
--- /dev/null
@@ -0,0 +1,103 @@
+require 'pp'
+
+module Redwood
+
+class Console
+  def initialize mode
+    @mode = mode
+  end
+
+  def query(query)
+    Enumerable::Enumerator.new(Index, :each_message, Index.parse_query(query))
+  end
+
+  def add_labels(query, *labels)
+    query(query).each { |m| m.labels += labels; m.save Index }
+  end
+
+  def remove_labels(query, *labels)
+    query(query).each { |m| m.labels -= labels; m.save Index }
+  end
+
+  def xapian; Index.instance.instance_variable_get :@xapian; end
+  def ferret; Index.instance.instance_variable_get :@index; end
+
+  ## files that won't cause problems when reloaded
+  ## TODO expand this list / convert to blacklist
+  RELOAD_WHITELIST = %w(sup/xapian_index.rb sup/modes/console-mode.rb)
+
+  def reload
+    old_verbose = $VERBOSE
+    $VERBOSE = nil
+    old_features = $".dup
+    begin
+      fs = $".grep(/^sup\//)
+      fs.reject! { |f| not RELOAD_WHITELIST.member? f }
+      fs.each { |f| $".delete f }
+      fs.each do |f|
+        @mode << "reloading #{f}\n"
+        begin
+          require f
+        rescue LoadError => e
+          raise unless e.message =~ /no such file to load/
+        end
+      end
+    rescue Exception
+      $".clear
+      $".concat old_features
+      raise
+    ensure
+      $VERBOSE = old_verbose
+    end
+    true
+  end
+
+  def clear_hooks
+    HookManager.clear
+    nil
+  end
+end
+
+class ConsoleMode < LogMode
+  register_keymap do |k|
+    k.add :run, "Restart evaluation", 'e'
+  end
+
+  def initialize
+    super "console"
+    @console = Console.new self
+    @binding = @console.instance_eval { binding }
+    self << <<EOS
+Sup #{VERSION} console.
+Available commands: #{(@console.methods - Object.methods) * ", "}
+Ctrl-g stops evaluation; 'e' restarts it.
+
+EOS
+  end
+
+  def execute cmd
+    begin
+      self << ">> #{cmd}\n"
+      ret = eval cmd, @binding
+      self << "=> #{ret.pretty_inspect}\n"
+    rescue Exception
+      self << "#{$!.class}: #{$!.message}\n"
+      clean_backtrace = []
+      $!.backtrace.each { |l| break if l =~ /console-mode/; clean_backtrace << l }
+      clean_backtrace.each { |l| self << "#{l}\n" }
+    end
+  end
+
+  def prompt
+    BufferManager.ask :console, "eval: "
+  end
+
+  def run
+    while true
+      cmd = prompt or return
+      execute cmd
+    end
+  end
+end
+
+end
index a48930aea521fd73d7c83f2bf9846d8be6a29025..8da316fe00593f8085adb167e0200b5a6c650d07 100644 (file)
@@ -325,7 +325,7 @@ protected
       BufferManager.flash "Message sent!"
       true
     rescue SystemCallError, SendmailCommandFailed, CryptoManager::Error => e
-      Redwood::log "Problem sending mail: #{e.message}"
+      warn "Problem sending mail: #{e.message}"
       BufferManager.flash "Problem sending mail: #{e.message}"
       false
     end
index 53287c1156611dc835ab87c571a27b52d7c092b6..f65ec2e2080f2f823b1fa102fb77d2cc5f4cbae5 100644 (file)
@@ -73,7 +73,7 @@ protected
       ##   TODO make the labelmanager responsible for label counts
       ## and then it can listen to labeled and unlabeled events, etc.
       if total == 0 && !LabelManager::RESERVED_LABELS.include?(label) && !LabelManager.new_label?(label)
-        Redwood::log "no hits for label #{label}, deleting"
+        debug "no hits for label #{label}, deleting"
         LabelManager.delete label
         next
       end
index de16b5e6635b988fa90329b7d66ef046d3793372..07fa9dd6cda8d4780676119839ee053baee41560 100644 (file)
@@ -1,36 +1,40 @@
+require 'stringio'
 module Redwood
 
+## a variant of text mode that allows the user to automatically follow text,
+## and respawns when << is called if necessary.
+
 class LogMode < TextMode
   register_keymap do |k|
     k.add :toggle_follow, "Toggle follow mode", 'f'
   end
 
-  def initialize
+  def initialize buffer_name
     @follow = true
-    super
+    @buffer_name = buffer_name
+    @on_kill = []
+    super()
   end
 
+  ## register callbacks for when the buffer is killed
+  def on_kill &b; @on_kill << b end
+
   def toggle_follow
     @follow = !@follow
-    if buffer
-      if @follow
-        jump_to_line lines - buffer.content_height + 1 # leave an empty line at bottom
-      end
-      buffer.mark_dirty
+    if @follow
+      jump_to_line(lines - buffer.content_height + 1) # leave an empty line at bottom
     end
+    buffer.mark_dirty
   end
 
-  def text= t
-    super
-    if buffer && @follow
-      follow_top = lines - buffer.content_height + 1
-      jump_to_line follow_top if topline < follow_top
+  def << s
+    unless buffer
+      BufferManager.spawn @buffer_name, self, :hidden => true, :system => true
     end
-  end
 
-  def << line
-    super
-    if buffer && @follow
+    s.split("\n").each { |l| super(l + "\n") } # insane. different << semantics.
+
+    if @follow
       follow_top = lines - buffer.content_height + 1
       jump_to_line follow_top if topline < follow_top
     end
@@ -39,6 +43,12 @@ class LogMode < TextMode
   def status
     super + " (follow: #@follow)"
   end
+
+  def cleanup
+    @on_kill.each { |cb| cb.call self }
+    self.text = ""
+    super
+  end
 end
 
 end
index 5849f3eed922e96a59519c2e99d972e8a0e98b4a..cf6134302a4992dca05557753ded1d5792df166c 100644 (file)
@@ -3,18 +3,16 @@ module Redwood
 class PollMode < LogMode
   def initialize
     @new = true
-    super
-  end
-
-  def puts s=""
-    self << s + "\n"
+    super "poll for new messages"
   end
 
   def poll
-    puts unless @new
-    @new = false
-    puts "Poll started at #{Time.now}"
-    PollManager.do_poll { |s| puts s }
+    unless @new
+      @new = false
+      self << "\n"
+    end
+    self << "Poll started at #{Time.now}\n"
+    PollManager.do_poll { |s| self << (s + "\n") }
   end
 end
 
index c79c5dbe51dd59df0426572c523a2b79492f524f..700dfc1f99d310a0290e0bfcab4e8478957ca678 100644 (file)
@@ -56,7 +56,7 @@ EOS
     ## don't check that it's an Account, though; assume they know what they're
     ## doing.
     if hook_reply_from && !(hook_reply_from.is_a? Person)
-      Redwood::log "reply-from returned non-Person, using default from."
+      info "reply-from returned non-Person, using default from."
       hook_reply_from = nil
     end
 
index 737f6f1ff309a5bd87ebdeb1d213c77c88b86bc2..bac4792e832daafb1ba9964d0e3cb2387c1bc9b6 100644 (file)
@@ -212,14 +212,14 @@ EOS
     bt = to.size > 1 ? "#{to.size} recipients" : to.to_s
 
     if BufferManager.ask_yes_or_no "Really bounce to #{bt}?"
-      Redwood::log "Bounce Command: #{cmd}"
+      debug "bounce command: #{cmd}"
       begin
         IO.popen(cmd, 'w') do |sm|
           sm.puts m.raw_message
         end
         raise SendmailCommandFailed, "Couldn't execute #{cmd}" unless $? == 0
       rescue SystemCallError, SendmailCommandFailed => e
-        Redwood::log "Problem sending mail: #{e.message}"
+        warn "problem sending mail: #{e.message}"
         BufferManager.flash "Problem sending mail: #{e.message}"
       end
     end
index 4374242cefe39af7682b1db80f34f9ac0a06dedf..46fe5c5ed2cc700c294e92a2a802be47fdd754cd 100644 (file)
@@ -35,22 +35,17 @@ EOS
     @thread = nil
     @last_poll = nil
     @polling = false
-
-    self.class.i_am_the_instance self
-  end
-
-  def buffer
-    b, new = BufferManager.spawn_unless_exists("poll for new messages", :hidden => true, :system => true) { PollMode.new }
-    b
+    @mode = nil
   end
 
   def poll
     return if @polling
     @polling = true
+    @mode ||= PollMode.new
     HookManager.run "before-poll"
 
     BufferManager.flash "Polling for new messages..."
-    num, numi, from_and_subj, from_and_subj_inbox = buffer.mode.poll
+    num, numi, from_and_subj, from_and_subj_inbox = @mode.poll
     if num > 0
       BufferManager.flash "Loaded #{num.pluralize 'new message'}, #{numi} to inbox." 
     else
@@ -88,7 +83,7 @@ EOS
         begin
           yield "Loading from #{source}... " unless source.done? || (source.respond_to?(:has_errors?) && source.has_errors?)
         rescue SourceError => e
-          Redwood::log "problem getting messages from #{source}: #{e.message}"
+          warn "problem getting messages from #{source}: #{e.message}"
           Redwood::report_broken_sources :force_to_top => true
           next
         end
@@ -112,7 +107,7 @@ EOS
             end
           else
             yield "Found new message at #{m.source_info} with labels #{m.labels.to_a * ','}"
-            Index.add_message m
+            add_new_message m
             num += 1
             from_and_subj << [m.from && m.from.longname, m.subj]
             if (m.labels & [:inbox, :spam, :deleted, :killed]) == Set.new([:inbox])
@@ -144,7 +139,7 @@ EOS
 
       source.each do |offset, source_labels|
         if source.has_errors?
-          Redwood::log "error loading messages from #{source}: #{source.error.message}"
+          warn "error loading messages from #{source}: #{source.error.message}"
           return
         end
 
@@ -157,7 +152,7 @@ EOS
         yield m
       end
     rescue SourceError => e
-      Redwood::log "problem getting messages from #{source}: #{e.message}"
+      warn "problem getting messages from #{source}: #{e.message}"
       Redwood::report_broken_sources :force_to_top => true
     end
   end
index 74fe1aebbb780a91f234323dd109b847022e51ab..9203dd666816a770d8537c18c6281ae8f17ed14a 100644 (file)
@@ -8,8 +8,6 @@ class SentManager
   def initialize source_uri
     @source = nil
     @source_uri = source_uri
-    self.class.i_am_the_instance self
-    Redwood::log "SentManager intialized with source uri: #@source_uri"
   end
 
   def source_id; @source.id; end
@@ -22,7 +20,6 @@ class SentManager
 
   def default_source
     @source = Recoverable.new SentLoader.new
-    Redwood::log "SentManager initializing default source: #@source."
     @source_uri = @source.uri
     @source
   end
index a557bd857c815232b07601f98d713fa9ae26ff2c..78386ff3fd19b0f74c75820827346df036f84c61 100644 (file)
@@ -139,7 +139,7 @@ class Source
       header[k] = begin
         Rfc2047.decode_to $encoding, v
       rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
-        #Redwood::log "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
+        #debug "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
         v
       end
     end
@@ -183,7 +183,6 @@ class SourceManager
     @sources = {}
     @sources_dirty = false
     @source_mutex = Monitor.new
-    self.class.i_am_the_instance self
   end
 
   def [](id)
diff --git a/lib/sup/suicide.rb b/lib/sup/suicide.rb
deleted file mode 100644 (file)
index 98b4346..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-module Redwood
-
-class SuicideManager
-  include Singleton
-
-  DELAY = 5
-
-  def initialize fn
-    @fn = fn
-    @die = false
-    @thread = nil
-    self.class.i_am_the_instance self
-    FileUtils.rm_f @fn
-  end
-
-  bool_reader :die
-
-  def start
-    @thread = Redwood::reporting_thread("suicide watch") do
-      while true
-        sleep DELAY
-        if File.exists? @fn
-          FileUtils.rm_f @fn
-          @die = true
-        end
-      end
-    end
-  end
-
-  def stop
-    @thread.kill if @thread
-    @thread = nil
-  end
-end
-
-end
index c748c7a653a6a3405a089c2cfe4149a17ba82de4..b8dec59d368af5508ee85b40ae78a3dade452f83 100644 (file)
@@ -112,11 +112,11 @@ class TextField
         unless @history.empty?
           value = get_cursed_value
           @i ||= @history.size
-          #Redwood::log "history before #{@history.inspect}"
+          #debug "history before #{@history.inspect}"
           @history[@i] = value #unless value =~ /^\s*$/
           @i = (@i + (c == Ncurses::KEY_UP ? -1 : 1)) % @history.size
           @value = @history[@i]
-          #Redwood::log "history after #{@history.inspect}"
+          #debug "history after #{@history.inspect}"
           set_cursed_value @value
           Ncurses::Form::REQ_END_FIELD
         end
index 5a93c31bc1dac91142c9c4bb509cd7ee3649756d..9ccf84a01724af3d7f7a2077e9f774f85d9579a8 100644 (file)
@@ -12,7 +12,6 @@ class UndoManager
 
   def initialize
     @@actionlist = []
-    self.class.i_am_the_instance self
   end
 
   def register desc, *actions, &b
index 021b7e1b09eb93276bc03d659d36fb749f5c9aec..d3868012cd0b3607c17914729a170bf9ff4fbb47 100644 (file)
@@ -16,7 +16,6 @@ class UpdateManager
 
   def initialize
     @targets = {}
-    self.class.i_am_the_instance self
   end
 
   def register o; @targets[o] = true; end
index adf44b7ccbd2b46f056a47fdf04dfa9a650275c5..aa3ee764ac59b276d7f4ae758d4b119e141832c2 100644 (file)
@@ -25,6 +25,7 @@ class Lockfile
   def lockinfo_on_disk
     h = load_lock_id IO.read(path)
     h['mtime'] = File.mtime path
+    h['path'] = path
     h
   end
 
@@ -495,19 +496,20 @@ class Time
   end
 end
 
-## simple singleton module. far less complete and insane than the ruby
-## standard library one, but automatically forwards methods calls and
-## allows for constructors that take arguments.
+## simple singleton module. far less complete and insane than the ruby standard
+## library one, but it automatically forwards methods calls and allows for
+## constructors that take arguments.
 ##
-## You must have #initialize call "self.class.i_am_the_instance self"
-## at some point or everything will fail horribly.
+## classes that inherit this can define initialize. however, you cannot call
+## .new on the class. To get the instance of the class, call .instance;
+## to create the instance, call init.
 module Singleton
   module ClassMethods
     def instance; @instance; end
     def instantiated?; defined?(@instance) && !@instance.nil?; end
     def deinstantiate!; @instance = nil; end
     def method_missing meth, *a, &b
-      raise "no instance defined!" unless defined? @instance
+      raise "no #{name} instance defined in method call to #{meth}!" unless defined? @instance
 
       ## if we've been deinstantiated, just drop all calls. this is
       ## useful because threads that might be active during the
@@ -517,13 +519,14 @@ module Singleton
 
       @instance.send meth, *a, &b
     end
-    def i_am_the_instance o
+    def init *args
       raise "there can be only one! (instance)" if defined? @instance
-      @instance = o
+      @instance = new(*args)
     end
   end
 
   def self.included klass
+    klass.private_class_method :allocate, :new
     klass.extend ClassMethods
   end
 end
@@ -651,7 +654,7 @@ class Iconv
     begin
       Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
     rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::InvalidCharacter, Iconv::IllegalSequence => e
-      Redwood::log "warning: error (#{e.class.name}) decoding text from #{charset} to #{target}: #{text[0 ... 20]}"
+      info "couldn't transcode text from #{charset} to #{target} (\"#{text[0 ... 20]}\"...)"
       text
     end
   end
index f79b05503f914d7b3c28dd3d030615402847d88d..dbf66431bc263ada5420b9a981f766c9ca5675d4 100644 (file)
@@ -221,10 +221,10 @@ class XapianIndex < BaseIndex
       field, name = $1, ($3 || $4)
       case field
       when "filename"
-        Redwood::log "filename - translated #{field}:#{name} to attachment:\"#{name.downcase}\""
+        debug "filename: translated #{field}:#{name} to attachment:\"#{name.downcase}\""
         "attachment:\"#{name.downcase}\""
       when "filetype"
-        Redwood::log "filetype - translated #{field}:#{name} to attachment_extension:#{name.downcase}"
+        debug "filetype: translated #{field}:#{name} to attachment_extension:#{name.downcase}"
         "attachment_extension:#{name.downcase}"
       end
     end
@@ -238,13 +238,13 @@ class XapianIndex < BaseIndex
         if realdate
           case field
           when "after"
-            Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate.end}"
+            debug "chronic: translated #{field}:#{datestr} to #{realdate.end}"
             "date:#{realdate.end.to_i}..#{lastdate}"
           when "before"
-            Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
+            debug "chronic: translated #{field}:#{datestr} to #{realdate.begin}"
             "date:#{firstdate}..#{realdate.end.to_i}"
           else
-            Redwood::log "chronic: translated #{field}:#{datestr} to #{realdate}"
+            debug "chronic: translated #{field}:#{datestr} to #{realdate}"
             "date:#{realdate.begin.to_i}..#{realdate.end.to_i}"
           end
         else
@@ -413,10 +413,10 @@ class XapianIndex < BaseIndex
     m.attachments.each { |a| text << [a, PREFIX['attachment']] }
 
     truncated_date = if m.date < MIN_DATE
-      Redwood::log "warning: adjusting too-low date #{m.date} for indexing"
+      debug "warning: adjusting too-low date #{m.date} for indexing"
       MIN_DATE
     elsif m.date > MAX_DATE
-      Redwood::log "warning: adjusting too-high date #{m.date} for indexing"
+      debug "warning: adjusting too-high date #{m.date} for indexing"
       MAX_DATE
     else
       m.date