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
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.lock_interactively or exit
begin
Redwood::start
Index.load
- trap("TERM") { |x| SuicideManager.please_die! }
+ $die = false
+ trap("TERM") { |x| $die = true }
trap("WINCH") { |x| BufferManager.sigwinch_happened! }
if(s = Redwood::SourceManager.source_for DraftManager.source_name)
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?
+ until Redwood::exceptions.nonempty? || $die
c = begin
Ncurses.nonblocking_getch
rescue Interrupt => e
bm.erase_flash
- action = begin
- if bm.handle_input c
+ action =
+ begin
+ if bm.handle_input c
+ :nothing
+ else
+ bm.resolve_input_with_keymap c, global_keymap
+ end
+ rescue InputSequenceAborted
:nothing
- else
- bm.resolve_input_with_keymap c, global_keymap
end
- rescue InputSequenceAborted
- :nothing
- end
case action
when :quit_now
break if bm.kill_all_buffers_safely
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
stop_cursing
Redwood::log "stopped cursing"
- if SuicideManager.instantiated? && SuicideManager.die?
+ if $die
Redwood::log "I've been ordered to commit seppuku. I obey!"
end
Redwood::start
index = Redwood::Index.new
-index.lock_or_die
+index.lock_interactively or exit
begin
Redwood::SourceManager.load_sources
opt :changed, "Scan over the entire source for messages that have been deleted, altered, or moved from another source. (In the case of mbox sources, this includes all messages AFTER an altered message.)"
opt :restored, "Operate only on those messages included in a dump file as specified by --restore which have changed state."
opt :all, "Operate on all messages in the source, regardless of newness or changedness."
- opt :start_at, "For --changed and --all, start at a particular offset.", :type => :int
+ opt :start_at, "For --changed, --restored and --all, start at a particular offset.", :type => :int
text <<EOS
Trollop::die :restored, "requires --restore" if opts[:restored] unless opts[:restore]
if opts[:start_at]
Trollop::die :start_at, "must be non-negative" if opts[:start_at] < 0
- Trollop::die :start_at, "requires either --changed or --all" unless opts[:changed] || opts[:all]
+ Trollop::die :start_at, "requires either --changed, --restored or --all" unless opts[:changed] || opts[:restored] || opts[:all]
end
target = [:new, :changed, :all, :restored].find { |x| opts[x] } || :new
end
seen = {}
-index.lock_or_die
+index.lock_interactively or exit
begin
index.load
Redwood::start
index = Redwood::Index.new
-index.lock_or_die
+index.lock_interactively or exit
deleted_fp, spam_fp = nil
unless opts[:dry_run]
remove_labels = (opts[:remove] || "").split(",").map { |l| l.intern }.uniq
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.new
+index.lock_interactively or exit
begin
- index = Redwood::Index.new
index.load
source_ids =
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
## now everything else (which can feel free to call Redwood::log at load time)
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"
def mutex; @mutex ||= Mutex.new; end
def sync &b; mutex.synchronize(&b); end
+ ## magically, this stuff seems to work now. i could swear it didn't
+ ## before. hm.
def nonblocking_getch
## INSANTIY
## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
def content_height; @height - 1; end
def content_width; @width; end
- def resize rows, cols
+ def resize rows, cols
return if cols == @width && rows == @height
@width = cols
@height = rows
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 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]
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
module Redwood
class BaseIndex
+ include InteractiveLock
+
class LockError < StandardError
def initialize h
@h = h
@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}..."
--- /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
need_blank = File.exists?(@filename) && !File.zero?(@filename)
File.open(@filename, "a") do |f|
f.puts if need_blank
- f.puts "From #{from_email} #{date}"
+ f.puts "From #{from_email} #{date.utc}"
yield f
end
end
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"
+ cmd = "/usr/bin/run-mailcap --action=view '#{@content_type}:#{path}'"
Redwood::log "running: #{cmd.inspect}"
- system cmd
+ 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
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
+++ /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 please_die!; @die = true end
-
- 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
def lockinfo_on_disk
h = load_lock_id IO.read(path)
h['mtime'] = File.mtime path
+ h['path'] = path
h
end
super
@index_mutex = Monitor.new
+ end
- @entries = MarshalledGDBM.new File.join(dir, "entries.db")
- @docids = MarshalledGDBM.new File.join(dir, "docids.db")
- @thread_members = MarshalledGDBM.new File.join(dir, "thread_members.db")
- @thread_ids = MarshalledGDBM.new File.join(dir, "thread_ids.db")
- @assigned_docids = GDBM.new File.join(dir, "assigned_docids.db")
+ def load_index
+ @entries = MarshalledGDBM.new File.join(@dir, "entries.db")
+ @docids = MarshalledGDBM.new File.join(@dir, "docids.db")
+ @thread_members = MarshalledGDBM.new File.join(@dir, "thread_members.db")
+ @thread_ids = MarshalledGDBM.new File.join(@dir, "thread_ids.db")
+ @assigned_docids = GDBM.new File.join(@dir, "assigned_docids.db")
- @xapian = Xapian::WritableDatabase.new(File.join(dir, "xapian"), Xapian::DB_CREATE_OR_OPEN)
+ @xapian = Xapian::WritableDatabase.new(File.join(@dir, "xapian"), Xapian::DB_CREATE_OR_OPEN)
@term_generator = Xapian::TermGenerator.new()
@term_generator.stemmer = Xapian::Stem.new(STEM_LANGUAGE)
@enquire = Xapian::Enquire.new @xapian
@enquire.docid_order = Xapian::Enquire::ASCENDING
end
- def load_index
- end
-
def save_index
end