module Redwood
-$exception = nil
-def reporting_thread
- ::Thread.new do
- begin
- yield
- rescue Exception => e
- $exception ||= e
- raise
- end
- end
-end
-module_function :reporting_thread
-
global_keymap = Keymap.new do |k|
k.add :quit, "Quit Redwood", 'q'
k.add :help, "Show help", 'H', '?'
bm.draw_screen
imode.load_more_threads ibuf.content_height
- # reporting_thread { sleep 3; PollManager.poll }
- #PollManager.start_thread
+ reporting_thread { sleep 5; PollManager.poll }
+ PollManager.start_thread
until $exception
bm.draw_screen
Index.save unless $exception # TODO: think about this
if $exception
- case $exception
- when IndexError
- $stderr.puts <<EOS
-An error occurred while parsing a message from source:
- #{$exception.source}.
-Typically, this means that the source has been modified in some
-way which has rendered the messages invalid. For example, if it's
-an mbox file, you may have read or deleted messages using another
-mail client.
-
-You must rebuild the index for this source. Please run:
- sup-import --rebuild #{$exception.source}
-to correct this error.
-EOS
-#' stupid ruby-mode
- else
- $stderr.puts <<EOS
+ $stderr.puts <<EOS
----------------------------------------------------------------
I'm very sorry, but it seems that an error occurred in Sup.
Please accept my sincere apologies. If you don't mind, please
The problem was: #{$exception.message} (error type #{$exception.class.name})
A backtrace follows:
EOS
- end
raise $exception
end
unless username && password
username = ask("Username for #{uri.host}: ");
password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
+ puts # why?
end
[username, password]
username, password = get_login_info uri, index.sources
Redwood::MBox::SSHLoader.new(uri, username, password, nil, !unusual, !!archive)
when %r!^imaps?://!
- username, password = get_login_info uri, sources
+ username, password = get_login_info uri, index.sources
Redwood::IMAP.new(uri, username, password, nil, !unusual, !!archive)
else
Redwood::MBox::Loader.new(uri, nil, !unusual, !!archive)
module Redwood
VERSION = "0.0.2"
- BASE_DIR = File.join(ENV["HOME"], ".sup")
+ BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
CONFIG_FN = File.join(BASE_DIR, "config.yaml")
SOURCE_FN = File.join(BASE_DIR, "sources.yaml")
LABEL_FN = File.join(BASE_DIR, "labels.txt")
YAML_DOMAIN = "masanjin.net"
YAML_DATE = "2006-10-01"
+## record exceptions thrown in threads nicely
+ $exception = nil
+ def reporting_thread
+ ::Thread.new do
+ begin
+ yield
+ rescue Exception => e
+ $exception ||= e
+ raise
+ end
+ end
+ end
+ module_function :reporting_thread
+
## one-stop shop for yamliciousness
def register_yaml klass, props
vars = props.map { |p| "@#{p}" }
lamer.first
end
+ def mutex; @mutex ||= Mutex.new; end
+ def sync &b; mutex.synchronize &b; end
+
## aaahhh, user input. who would have though that such a simple
## idea would be SO FUCKING COMPLICATED?! because apparently
## Ncurses.getch (and Curses.getch), even in cbreak mode, BLOCKS
end
end
- module_function :rows, :cols, :nonblocking_getch
+ module_function :rows, :cols, :nonblocking_getch, :mutex, :sync
KEY_CANCEL = "\a"[0] # ctrl-g
end
module Redwood
-## could be a bottleneck, but doesn't seem to significantly slow
-## things down.
-
-class SafeNcurses
- def self.method_missing meth, *a, &b
- @mutex ||= Mutex.new
- @mutex.synchronize { Ncurses.send meth, *a, &b }
- end
-end
-
class Buffer
attr_reader :mode, :x, :y, :width, :height, :title
bool_reader :dirty
def content_width; @width; end
def resize rows, cols
- return if rows == @width && cols == @height
+ return if cols == @width && rows == @height
@width = cols
@height = rows
+ @dirty = true
mode.resize rows, cols
end
draw_status
commit
end
+
def mark_dirty; @dirty = true; end
def commit
@minibuf_stack = []
@textfields = {}
@flash = nil
- @freeze = false
+ @shelled = false
self.class.i_am_the_instance self
end
def buffers; @name_map.to_a; end
def focus_on buf
- raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless
- @buffers.member? buf
+ raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
return if buf == @focus_buf
@focus_buf.blur if @focus_buf
@focus_buf = buf
end
def raise_to_front buf
- raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless
- @buffers.member? buf
+ raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
@buffers.delete buf
@buffers.push buf
focus_on buf
end
def completely_redraw_screen
- return if @freeze
- SafeNcurses.clear
- @dirty = true
- draw_screen
+ return if @shelled
+
+ Ncurses.sync do
+ @dirty = true
+ Ncurses.clear
+ draw_screen :sync => false
+ end
end
def handle_resize
- return if @freeze
- rows, cols = SafeNcurses.rows, SafeNcurses.cols
+ return if @shelled
+ rows, cols = Ncurses.rows, Ncurses.cols
@buffers.each { |b| b.resize rows - minibuf_lines, cols }
completely_redraw_screen
- flash "resized to #{rows}x#{cols}"
+ flash "Resized to #{rows}x#{cols}"
end
- def draw_screen skip_minibuf=false
- return if @freeze
+ def draw_screen opts={}
+ return if @shelled
+
+ Ncurses.mutex.lock unless opts[:sync] == false
## disabling this for the time being, to help with debugging
## (currently we only have one buffer visible at a time).
## TODO: reenable this if we allow multiple buffers
false && @buffers.inject(@dirty) do |dirty, buf|
- buf.resize SafeNcurses.rows - minibuf_lines, SafeNcurses.cols
+ buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
@dirty ? buf.draw : buf.redraw
end
+
## quick hack
if true
buf = @buffers.last
- buf.resize SafeNcurses.rows - minibuf_lines, SafeNcurses.cols
+ buf.resize Ncurses.rows - minibuf_lines, Ncurses.cols
+ File.open("asdf.txt", "a") { |f| f.puts "dirty #@dirty, (re)drawing #{buf.mode.name}" }
@dirty ? buf.draw : buf.redraw
end
- draw_minibuf unless skip_minibuf
+ draw_minibuf :sync => false unless opts[:skip_minibuf]
@dirty = false
- SafeNcurses.doupdate
+ Ncurses.doupdate
+ Ncurses.refresh if opts[:refresh]
+ Ncurses.mutex.unlock unless opts[:sync] == false
end
## gets the mode from the block, which is only called if the buffer
num += 1
end
- Redwood::log "spawning buffer \"#{realtitle}\""
- width = opts[:width] || SafeNcurses.cols
- height = opts[:height] || SafeNcurses.rows - 1
+ width = opts[:width] || Ncurses.cols
+ height = opts[:height] || Ncurses.rows - 1
## since we are currently only doing multiple full-screen modes,
## use stdscr for each window. once we become more sophisticated,
##
## w = Ncurses::WINDOW.new(height, width, (opts[:top] || 0),
## (opts[:left] || 0))
- w = SafeNcurses.stdscr
+ w = Ncurses.stdscr
b = Buffer.new w, mode, width, height, :title => realtitle
mode.buffer = b
@name_map[realtitle] = b
def kill_buffer buf
raise ArgumentError, "buffer not on stack: #{buf.inspect}" unless @buffers.member? buf
- Redwood::log "killing buffer \"#{buf.title}\""
buf.mode.cleanup
@buffers.delete buf
end
def ask domain, question, default=nil
- @textfields[domain] ||= TextField.new SafeNcurses.stdscr, SafeNcurses.rows - 1, 0,
- SafeNcurses.cols
+ @textfields[domain] ||= TextField.new Ncurses.stdscr, Ncurses.rows - 1, 0, Ncurses.cols
tf = @textfields[domain]
## this goddamn ncurses form shit is a fucking 1970's
## that needs to happen in order to display a form and have the
## entire screen not disappear and have the cursor in the right
## place is TOO FUCKING COMPLICATED.
- tf.activate question, default
- @dirty = true
- draw_screen true
+ Ncurses.sync do
+ tf.activate question, default
+ @dirty = true
+ draw_screen :skip_minibuf => true, :sync => false
+ end
ret = nil
- @freeze = true
tf.position_cursor
- SafeNcurses.refresh
- while tf.handle_input(SafeNcurses.nonblocking_getch); end
- @freeze = false
+ Ncurses.sync { Ncurses.refresh }
+ while tf.handle_input(Ncurses.nonblocking_getch); end
ret = tf.value
- tf.deactivate
+ Ncurses.sync { tf.deactivate }
@dirty = true
ret
accept = accept.split(//).map { |x| x[0] } if accept
flash question
- SafeNcurses.curs_set 1
- SafeNcurses.move SafeNcurses.rows - 1, question.length + 1
- SafeNcurses.refresh
+ Ncurses.sync do
+ Ncurses.curs_set 1
+ Ncurses.move Ncurses.rows - 1, question.length + 1
+ Ncurses.refresh
+ end
ret = nil
done = false
- @freeze = true
+ @shelled = true
until done
- key = SafeNcurses.nonblocking_getch
+ key = Ncurses.nonblocking_getch
if key == Ncurses::KEY_CANCEL
done = true
elsif (accept && accept.member?(key)) || !accept
done = true
end
end
- @freeze = false
- SafeNcurses.curs_set 0
- erase_flash
- draw_screen
- SafeNcurses.curs_set 0
+
+ @shelled = false
+
+ Ncurses.sync do
+ SafeNcurses.curs_set 0
+ erase_flash
+ draw_screen :sync => false
+ SafeNcurses.curs_set 0
+ end
ret
end
def minibuf_lines; [(@flash ? 1 : 0) + @minibuf_stack.compact.size, 1].max; end
- def draw_minibuf
- SafeNcurses.attrset Colormap.color_for(:none)
+ def draw_minibuf opts={}
m = @minibuf_stack.compact
m << @flash if @flash
m << "" if m.empty?
+
+ Ncurses.mutex.lock unless opts[:sync] == false
+ Ncurses.attrset Colormap.color_for(:none)
m.each_with_index do |s, i|
- SafeNcurses.mvaddstr SafeNcurses.rows - i - 1, 0, s + (" " * [SafeNcurses.cols - s.length, 0].max)
+ Ncurses.mvaddstr Ncurses.rows - i - 1, 0, s + (" " * [Ncurses.cols - s.length, 0].max)
end
+ Ncurses.refresh if opts[:refresh]
+ Ncurses.mutex.unlock unless opts[:sync] == false
end
def say s, id=nil
+ new_id = id.nil?
id ||= @minibuf_stack.length
@minibuf_stack[id] = s
- unless @freeze
- draw_screen
- SafeNcurses.refresh
+ if new_id
+ draw_screen :refresh => true
+ else
+ draw_minibuf :refresh => true
end
+
if block_given?
begin
yield
def flash s
@flash = s
- unless @freeze
- draw_screen
- SafeNcurses.refresh
- end
+ draw_screen :refresh => true
end
## a little tricky because we can't just delete_at id because ids
@minibuf_stack[id] = nil
if id == @minibuf_stack.length - 1
id.downto(0) do |i|
- break unless @minibuf_stack[i].nil?
+ break if @minibuf_stack[i]
@minibuf_stack.delete_at i
end
end
- unless @freeze
- draw_screen
- SafeNcurses.refresh
- end
+ draw_screen :refresh => true
end
def shell_out command
- @freeze = true
- SafeNcurses.endwin
- system command
- SafeNcurses.refresh
- SafeNcurses.curs_set 0
- @freeze = false
+ @shelled = true
+ Ncurses.sync do
+ Ncurses.endwin
+ system command
+ Ncurses.refresh
+ Ncurses.curs_set 0
+ end
+ @shelled = false
end
end
end
@mutex = Mutex.new
end
+ def say s
+ @say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
+ Redwood::log s
+ end
+ def shutup
+ BufferManager.clear @say_id if BufferManager.instantiated?
+ @say_id = nil
+ end
+ private :say, :shutup
+
def connect
return false if broken?
return true if @imap
##
## FUCK!!!!!!!!!
- Redwood::log "connecting to #{@parsed_uri.host} port #{ssl? ? 993 : 143}, ssl=#{ssl?} ..."
- sid = BufferManager.say "Connecting to IMAP server #{host}..." if BufferManager.instantiated?
+ say "Connecting to IMAP server #{host}:#{port}..."
Redwood::reporting_thread do
begin
#raise Net::IMAP::ByeResponseError, "simulated imap failure"
- # @imap = Net::IMAP.new host, ssl? ? 993 : 143, ssl?
- sleep 3
- BufferManager.say "Logging in...", sid if BufferManager.instantiated?
- # @imap.authenticate 'LOGIN', @username, @password
- sleep 3
- BufferManager.say "Sizing mailbox...", sid if BufferManager.instantiated?
- # @imap.examine mailbox
- # last_id = @imap.responses["EXISTS"][-1]
- sleep 1
+ @imap = Net::IMAP.new host, ssl? ? 993 : 143, ssl?
+ say "Logging in..."
+ @imap.authenticate 'LOGIN', @username, @password
+ say "Sizing mailbox..."
+ @imap.examine mailbox
+ last_id = @imap.responses["EXISTS"][-1]
- BufferManager.say "Reading headers (because IMAP sucks)...", sid if BufferManager.instantiated?
- # values = @imap.fetch(1 .. last_id, ['RFC822.SIZE', 'INTERNALDATE'])
- sleep 3
+ say "Reading headers (because IMAP sucks)..."
+ values = @imap.fetch(1 .. last_id, ['RFC822.SIZE', 'INTERNALDATE'])
- raise Net::IMAP::ByeResponseError, "simulated imap failure"
- Redwood::log "successfully connected to #{@parsed_uri}"
+ say "Successfully connected to #{@parsed_uri}"
values.each do |v|
id = make_id v
self.broken_msg = e.message.chomp # fucking chomp! fuck!!!
@imap = nil
Redwood::log "error connecting to IMAP server: #{self.broken_msg}"
- ensure
- BufferManager.clear sid if BufferManager.instantiated?
+ ensure
+ shutup
end
end.join
- @mutex.unlock
!!@imap
end
private :connect
def make_id imap_stuff
msize, mdate = imap_stuff.attr['RFC822.SIZE'], Time.parse(imap_stuff.attr["INTERNALDATE"])
- sprintf("%d.%07d", mdate.to_i, msize).to_i
+ sprintf("%d%07d", mdate.to_i, msize).to_i
end
private :make_id
def host; @parsed_uri.host; end
- def mailbox; @parsed_uri.path[1..-1] end ##XXXX TODO handle nil
+ def port; @parsed_uri.port || (ssl? ? 993 : 143); end
+ def mailbox; @parsed_uri.path[1..-1] || 'INBOX'; end
def ssl?; @parsed_uri.scheme == 'imaps' end
def load_header id
def get_imap_field id, field
f = nil
- @mutex.synchronize do
- imap_id = @imap_ids[id] or raise SourceError, "Unknown message id #{id}. It is likely that messages have been deleted from this IMAP mailbox."
- begin
- f = @imap.fetch imap_id, [field, 'RFC822.SIZE', 'INTERNALDATE']
- got_id = make_id f
- raise SourceError, "IMAP message mismatch: requested #{id}, got #{got_id}. It is likely the IMAP mailbox has been modified." unless got_id == id
- rescue Net::IMAP::Error => e
- raise SourceError, e.message
- end
- raise SourceError, "null IMAP field '#{field}' for message with id #{id} imap id #{imap_id}" if f.nil?
+ imap_id = @imap_ids[id] or raise SourceError, "Unknown message id #{id}. It is likely that messages have been deleted from this IMAP mailbox."
+ begin
+ f = @imap.fetch imap_id, [field, 'RFC822.SIZE', 'INTERNALDATE']
+ got_id = make_id f[0]
+ raise SourceError, "IMAP message mismatch: requested #{id}, got #{got_id}. It is likely the IMAP mailbox has been modified." unless got_id == id
+ rescue Net::IMAP::Error => e
+ raise SourceError, e.message
end
+ raise SourceError, "null IMAP field '#{field}' for message with id #{id} imap id #{imap_id}" if f.nil?
+
f[0].attr[field]
end
private :get_imap_field
@mutex.synchronize { connect or raise SourceError, broken_msg }
@ids.first
end
+
def end_offset
@mutex.synchronize { connect or raise SourceError, broken_msg }
@ids.last
module Redwood
-class IndexError < StandardError
- attr_reader :source
-
- def initialize source, s
- super s
- @source = source
- end
-end
-
class Index
include Singleton
## the file-like interface to a remote file
class SSHFile
MAX_BUF_SIZE = 1024 * 1024 # bytes
- MAX_TRANSFER_SIZE = 1024 * 64
+ MAX_TRANSFER_SIZE = 1024 * 128
REASONABLE_TRANSFER_SIZE = 1024 * 32
SIZE_CHECK_INTERVAL = 60 * 1 # seconds
@say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
Redwood::log s
end
- private :say
-
def shutup
BufferManager.clear @say_id if BufferManager.instantiated?
@say_id = nil
end
+ private :say, :shutup
def connect
return if @session
begin
#raise SSHFileError, "simulated SSH file error"
- #@session = Net::SSH.start @host, @ssh_opts
- sleep 3
+ @session = Net::SSH.start @host, @ssh_opts
say "Starting SSH shell..."
- # @shell = @session.shell.sync
- sleep 3
+ @shell = @session.shell.sync
say "Checking for #@fn..."
- sleep 1
- raise Errno::ENOENT, @fn
raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0
ensure
shutup
begin
retries = 0
connect
- MBox::debug "sending command: #{cmd.inspect}"
+ # MBox::debug "sending command: #{cmd.inspect}"
begin
result = @shell.send_command cmd
raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{result.stderr[0 .. 100]}" unless result.status == 0
if @curpos == topline
page_up
set_cursor_pos [botline - 2, topline].max
-# raise "cursor position now #@curpos, topline #{topline} botline #{botline}"
else
@curpos -= 1
unless buffer.dirty?
:highlight => opts[:highlight]
when Array
xpos = 0
+
+ ## speed test
+ # str = s.map { |color, text| text }.join
+ # buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
+ # return
+
s.each do |color, text|
raise "nil text for color '#{color}'" if text.nil? # good for debugging
if xpos + text.length < @leftcol