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
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
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
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
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
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
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]
unless $opts[:no_threads]
PollManager.start
- SuicideManager.start
Index.start_lock_update_thread
end
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 =
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
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
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
$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
$terminal.wrap_at = :auto
Redwood::start
-index = Redwood::Index.new
+index = Redwood::Index.init
Redwood::SourceManager.load_sources
say <<EOS
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|
require "sup"
Redwood::start
puts "loading index..."
-index = Redwood::Index.new
+index = Redwood::Index.init
index.load
puts "loaded index of #{index.size} messages"
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 = {}
end
seen = {}
-index.lock_or_die
+index.lock_interactively or exit
begin
index.load
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]
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]
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
## 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"
require "sup/person"
require "sup/account"
require "sup/thread"
+require "sup/interactive-lock"
require "sup/index"
require "sup/textfield"
require "sup/colormap"
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|
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
## 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
@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
## 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
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
@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})"
## 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] = []
## 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
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
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
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
@a2p[aalias] = p unless aalias.nil? || aalias.empty?
end
end
-
- self.class.i_am_the_instance self
end
def contacts; @p2a.keys end
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
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
def initialize dir
@dir = dir
@source = nil
- self.class.i_am_the_instance self
end
def self.source_name; "sup://drafts"; end
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
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
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
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
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
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
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] }
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
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]
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]
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
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
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
end
def log s
- Redwood::log "hook[#@__name]: #{s}"
+ info "hook[#@__name]: #{s}"
end
def ask_yes_or_no q
@tags = {}
Dir.mkdir dir unless File.exists? dir
-
- self.class.i_am_the_instance self
end
attr_reader :tags
def enabled? name; !hook_for(name).nil? end
+ def clear; @hooks.clear; end
+
private
def hook_for name
end
def log m
- Redwood::log("hook: " + m)
+ info("hook: " + m)
end
end
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
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
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
def say s
@say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
- Redwood::log s
+ debug s
end
def shutup
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
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
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
@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
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
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
--- /dev/null
+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
@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
+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
initial_poll = @ids.empty?
- Redwood::log "scanning maildir #@dir..."
+ debug "scanning maildir #@dir..."
begin
@mtimes.each_key do |d|
subdir = File.join(@dir, d)
@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!
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
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
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
## 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
## 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
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
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
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
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
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)
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
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
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
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
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
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
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
--- /dev/null
+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
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
## 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
+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
def status
super + " (follow: #@follow)"
end
+
+ def cleanup
+ @on_kill.each { |cb| cb.call self }
+ self.text = ""
+ super
+ end
end
end
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
## 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
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
@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
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
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])
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
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
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
def default_source
@source = Recoverable.new SentLoader.new
- Redwood::log "SentManager initializing default source: #@source."
@source_uri = @source.uri
@source
end
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
@sources = {}
@sources_dirty = false
@source_mutex = Monitor.new
- self.class.i_am_the_instance self
end
def [](id)
+++ /dev/null
-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
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
def initialize
@@actionlist = []
- self.class.i_am_the_instance self
end
def register desc, *actions, &b
def initialize
@targets = {}
- self.class.i_am_the_instance self
end
def register o; @targets[o] = true; end
def lockinfo_on_disk
h = load_lock_id IO.read(path)
h['mtime'] = File.mtime path
+ h['path'] = path
h
end
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
@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
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
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
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
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