]> git.cworth.org Git - sup/blob - bin/sup
remove welcome to sup message
[sup] / bin / sup
1 #!/usr/bin/env ruby
2
3 require 'rubygems'
4 require 'ncurses'
5 require 'curses'
6 require 'fileutils'
7 require 'trollop'
8 require 'fastthread'
9 require "sup"
10
11 BIN_VERSION = "git"
12
13 unless Redwood::VERSION == BIN_VERSION
14   $stderr.puts <<EOS
15
16 Error: version mismatch!
17 The sup executable is at version #{BIN_VERSION.inspect}.
18 The sup libraries are at version #{Redwood::VERSION.inspect}.
19
20 Is your development environment conflicting with rubygems?
21 EOS
22   exit(-1)
23 end
24
25 $opts = Trollop::options do
26   version "sup v#{Redwood::VERSION}"
27   banner <<EOS
28 Sup is a curses-based email client.
29
30 Usage:
31   sup [options]
32
33 Options are:
34 EOS
35   opt :list_hooks, "List all hooks and descriptions, and quit."
36   opt :no_threads, "Turn off threading. Helps with debugging. (Necessarily disables background polling for new messages.)"
37   opt :no_initial_poll, "Don't poll for new messages when starting."
38   opt :search, "Search for this query upon startup", :type => String
39   opt :compose, "Compose message to this recipient upon startup", :type => String
40 end
41
42 Redwood::HookManager.register "startup", <<EOS
43 Executes at startup
44 No variables.
45 No return value.
46 EOS
47
48 Redwood::HookManager.register "shutdown", <<EOS 
49 Executes when sup is shutting down. May be run when sup is crashing,
50 so don\'t do anything too important. Run before the label, contacts,
51 and people are saved.
52 No variables.
53 No return value.
54 EOS
55
56 if $opts[:list_hooks]
57   Redwood::HookManager.print_hooks
58   exit
59 end
60
61 Thread.abort_on_exception = true # make debugging possible
62
63 module Redwood
64
65 global_keymap = Keymap.new do |k|
66   k.add :quit_ask, "Quit Sup, but ask first", 'q'
67   k.add :quit_now, "Quit Sup immediately", 'Q'
68   k.add :help, "Show help", '?'
69   k.add :roll_buffers, "Switch to next buffer", 'b'
70 #  k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
71   k.add :kill_buffer, "Kill the current buffer", 'x'
72   k.add :list_buffers, "List all buffers", 'B'
73   k.add :list_contacts, "List contacts", 'C'
74   k.add :redraw, "Redraw screen", :ctrl_l
75   k.add :search, "Search all messages", '\\', 'F'
76   k.add :search_unread, "Show all unread messages", 'U'
77   k.add :list_labels, "List labels", 'L'
78   k.add :poll, "Poll for new messages", 'P'
79   k.add :compose, "Compose new message", 'm', 'c'
80   k.add :nothing, "Do nothing", :ctrl_g
81   k.add :recall_draft, "Edit most recent draft message", 'R'
82 end
83
84 def start_cursing
85   Ncurses.initscr
86   Ncurses.noecho
87   Ncurses.cbreak
88   Ncurses.stdscr.keypad 1
89   Ncurses.curs_set 0
90   Ncurses.start_color
91   $cursing = true
92 end
93
94 def stop_cursing
95   return unless $cursing
96   Ncurses.curs_set 1
97   Ncurses.echo
98   Ncurses.endwin
99 end
100 module_function :start_cursing, :stop_cursing
101
102 Index.new
103 begin
104   Index.lock
105 rescue Index::LockError => e
106   require 'highline'
107
108   h = HighLine.new
109   h.wrap_at = :auto
110   h.say Index.fancy_lock_error_message_for(e)
111
112   case h.ask("Should I ask that process to kill itself? ")
113   when /^\s*y(es)?\s*$/i
114     h.say "Ok, suggesting seppuku..."
115     FileUtils.touch Redwood::SUICIDE_FN
116     sleep SuicideManager::DELAY * 2
117     FileUtils.rm_f Redwood::SUICIDE_FN
118     h.say "Let's try that again."
119     retry
120   else
121     h.say <<EOS
122 Ok, giving up. If the process crashed and left a stale lockfile, you
123 can fix this by manually deleting #{Index.lockfile}.
124 EOS
125     exit
126   end
127 end
128
129 begin
130   Redwood::start
131   Index.load
132
133   if(s = Index.source_for DraftManager.source_name)
134     DraftManager.source = s
135   else
136     Redwood::log "no draft source, auto-adding..."
137     Index.add_source DraftManager.new_source
138   end
139
140   if(s = Index.source_for SentManager.source_name)
141     SentManager.source = s
142   else
143     Redwood::log "no sent mail source, auto-adding..."
144     Index.add_source SentManager.new_source
145   end
146
147   HookManager.run "startup"
148
149   log "starting curses"
150   start_cursing
151
152   bm = BufferManager.new
153   Colormap.new.populate_colormap
154
155   log "initializing mail index buffer"
156   imode = InboxMode.new
157   ibuf = bm.spawn "Inbox", imode
158
159   log "ready for interaction!"
160   Logger.make_buf
161
162   bm.draw_screen
163
164   Index.usual_sources.each do |s|
165     next unless s.respond_to? :connect
166     reporting_thread("call #connect on #{s}") do
167       begin
168         s.connect
169       rescue SourceError => e
170         Redwood::log "fatal error loading from #{s}: #{e.message}"
171       end
172     end
173   end unless $opts[:no_initial_poll]
174   
175   imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
176
177   if $opts[:compose]
178     ComposeMode.spawn_nicely :to_default => $opts[:compose]
179   end
180
181   unless $opts[:no_threads]
182     PollManager.start
183     SuicideManager.start
184     Index.start_lock_update_thread
185   end
186
187   if $opts[:search]
188     SearchResultsMode.spawn_from_query $opts[:search]
189   end
190
191   until Redwood::exceptions.nonempty? || SuicideManager.die?
192     c = 
193        begin
194          Ncurses.nonblocking_getch
195        rescue Exception => e
196          if e.is_a?(Interrupt)
197            raise if BufferManager.ask_yes_or_no("Die ungracefully now?")
198            bm.draw_screen
199            nil
200          end
201        end
202     next unless c
203     bm.erase_flash
204
205     action =
206       begin
207         if bm.handle_input c
208           :nothing
209         else
210           bm.resolve_input_with_keymap c, global_keymap
211         end
212       rescue InputSequenceAborted
213         :nothing
214       end
215     case action
216     when :quit_now
217       break if bm.kill_all_buffers_safely
218     when :quit_ask
219       if bm.ask_yes_or_no "Really quit?"
220         break if bm.kill_all_buffers_safely
221       end
222     when :help
223       curmode = bm.focus_buf.mode
224       bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
225     when :roll_buffers
226       bm.roll_buffers
227     when :roll_buffers_backwards
228       bm.roll_buffers_backwards
229     when :kill_buffer
230       bm.kill_buffer_safely bm.focus_buf
231     when :list_buffers
232       bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
233     when :list_contacts
234       b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
235       b.mode.load_in_background if new
236     when :search
237       query = BufferManager.ask :search, "search all messages: "
238       next unless query && query !~ /^\s*$/
239       SearchResultsMode.spawn_from_query query
240     when :search_unread
241       SearchResultsMode.spawn_from_query "is:unread"
242     when :list_labels
243       labels = LabelManager.listable_labels.map { |l| LabelManager.string_for l }
244       user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
245       unless user_label.nil?
246         if user_label.empty?
247           bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty?
248         else
249           LabelSearchResultsMode.spawn_nicely user_label
250         end
251       end
252     when :compose
253       ComposeMode.spawn_nicely
254     when :poll
255       reporting_thread("user-invoked poll") { PollManager.poll }
256     when :recall_draft
257       case Index.num_results_for :label => :draft
258       when 0
259         bm.flash "No draft messages."
260       when 1
261         m = nil
262         Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
263         r = ResumeMode.new(m)
264         BufferManager.spawn "Edit message", r
265         r.edit_message
266       else
267         b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
268         b.mode.load_threads :num => b.content_height if new
269       end
270     when :nothing, InputSequenceAborted
271     when :redraw
272       bm.completely_redraw_screen
273     else
274       bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}."
275     end
276
277     bm.draw_screen
278   end
279
280   bm.kill_all_buffers if SuicideManager.die?
281 rescue Exception => e
282   Redwood::record_exception e, "main"
283 ensure
284   unless $opts[:no_threads]
285     PollManager.stop if PollManager.instantiated?
286     SuicideManager.stop if PollManager.instantiated?
287     Index.stop_lock_update_thread
288   end
289
290   HookManager.run "shutdown"
291
292   Redwood::finish
293   stop_cursing
294   Redwood::log "stopped cursing"
295
296   if SuicideManager.instantiated? && SuicideManager.die?
297     Redwood::log "I've been ordered to commit seppuku. I obey!"
298   end
299
300   if Redwood::exceptions.empty?
301     Redwood::log "no fatal errors. good job, william."
302     Index.save
303   else
304     Redwood::log "oh crap, an exception"
305   end
306
307   Index.unlock
308 end
309
310 unless Redwood::exceptions.empty?
311   File.open(File.join(BASE_DIR, "exception-log.txt"), "w") do |f|
312     Redwood::exceptions.each do |e, name|
313       f.puts "--- #{e.class.name} from thread: #{name}"
314       f.puts e.message, e.backtrace
315     end
316   end
317   $stderr.puts <<EOS
318 ----------------------------------------------------------------
319 I'm very sorry. It seems that an error occurred in Sup. Please
320 accept my sincere apologies. If you don't mind, please send the
321 contents of ~/.sup/exception-log.txt and a brief report of the
322 circumstances to sup-talk at rubyforge dot orgs so that I might
323 address this problem. Thank you!
324
325 Sincerely,
326 William
327 ----------------------------------------------------------------
328 EOS
329   Redwood::exceptions.each do |e, name|
330     puts "--- #{e.class.name} from thread: #{name}"
331     puts e.message, e.backtrace
332   end
333 end
334
335 end