rbcurse 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. data/Makefile +21 -0
  2. data/Manifest.txt +6 -0
  3. data/README.markdown +6 -4
  4. data/TODO +372 -0
  5. data/TODO2.txt +121 -0
  6. data/VERSION +1 -1
  7. data/examples/README.txt +67 -0
  8. data/examples/abasiclist.rb +33 -0
  9. data/examples/alpmenu.rb +42 -0
  10. data/examples/app.rb +859 -0
  11. data/examples/app.sample +17 -0
  12. data/examples/appdirtree.rb +74 -0
  13. data/examples/appemail.rb +191 -0
  14. data/examples/appemaillb.rb +308 -0
  15. data/examples/appgcompose.rb +315 -0
  16. data/examples/atree.rb +64 -0
  17. data/examples/common/file.rb +40 -0
  18. data/examples/common/rmail.rb +257 -0
  19. data/examples/data.txt +683 -0
  20. data/examples/data/README.markdown +9 -0
  21. data/examples/data/brew.txt +38 -0
  22. data/examples/data/color.2 +37 -0
  23. data/examples/data/gemlist.txt +60 -0
  24. data/examples/data/lotr.txt +12 -0
  25. data/examples/data/ports.txt +136 -0
  26. data/examples/data/tasks.txt +27 -0
  27. data/examples/data/todocsv.csv +28 -0
  28. data/examples/data/unix1.txt +21 -0
  29. data/examples/data/unix2.txt +11 -0
  30. data/examples/dbdemo.rb +495 -0
  31. data/examples/deprecated/appgmail.rb +952 -0
  32. data/examples/deprecated/splitp.rb +56 -0
  33. data/examples/deprecated/testscrolllb.rb +86 -0
  34. data/examples/deprecated/testscrollp.rb +88 -0
  35. data/examples/deprecated/testscrollta.rb +80 -0
  36. data/examples/deprecated/testscrolltable.rb +165 -0
  37. data/examples/deprecated/testsplit.rb +87 -0
  38. data/examples/deprecated/testsplit2.rb +123 -0
  39. data/examples/deprecated/testsplit3.rb +215 -0
  40. data/examples/deprecated/testsplit3_1.rb +244 -0
  41. data/examples/deprecated/testsplit3a.rb +215 -0
  42. data/examples/deprecated/testsplit3b.rb +237 -0
  43. data/examples/deprecated/testsplitta.rb +148 -0
  44. data/examples/deprecated/testsplittv.rb +142 -0
  45. data/examples/deprecated/testsplittvv.rb +144 -0
  46. data/examples/deprecated/testtpane.rb +215 -0
  47. data/examples/deprecated/testtpane2.rb +145 -0
  48. data/examples/deprecated/testtpanetable.rb +203 -0
  49. data/examples/dirtree.rb +88 -0
  50. data/examples/experimental/resultsetdemo.rb +280 -0
  51. data/examples/experimental/testmform.rb +35 -0
  52. data/examples/experimental/testscroller.rb +117 -0
  53. data/examples/experimental/teststackflow.rb +111 -0
  54. data/examples/menu1.rb +112 -0
  55. data/examples/multispl.rb +86 -0
  56. data/examples/newmessagebox.rb +131 -0
  57. data/examples/newtabbedwindow.rb +100 -0
  58. data/examples/newtesttabp.rb +121 -0
  59. data/examples/qdfilechooser.rb +68 -0
  60. data/examples/rfe.rb +1239 -0
  61. data/examples/rfe_renderer.rb +121 -0
  62. data/examples/sqlc.rb +454 -0
  63. data/examples/sqlm.rb +437 -0
  64. data/examples/sqlt.rb +408 -0
  65. data/examples/status.txt +68 -0
  66. data/examples/table1.rb +24 -0
  67. data/examples/term2.rb +84 -0
  68. data/examples/test1.rb +239 -0
  69. data/examples/test2.rb +674 -0
  70. data/examples/testapp.rb +44 -0
  71. data/examples/testapp2.rb +58 -0
  72. data/examples/testchars.rb +137 -0
  73. data/examples/testcombo.rb +91 -0
  74. data/examples/testkeypress.rb +66 -0
  75. data/examples/testlistbox.rb +113 -0
  76. data/examples/testmenu.rb +101 -0
  77. data/examples/testmulticomp.rb +70 -0
  78. data/examples/testmulticontainer.rb +94 -0
  79. data/examples/testmultispl.rb +199 -0
  80. data/examples/testree.rb +106 -0
  81. data/examples/testtable.rb +264 -0
  82. data/examples/testtabp.rb +107 -0
  83. data/examples/testtodo.rb +584 -0
  84. data/examples/testvimsplit.rb +112 -0
  85. data/examples/testwsshortcuts.rb +64 -0
  86. data/examples/testwsshortcuts2.rb +126 -0
  87. data/examples/todo.db +0 -0
  88. data/examples/todo.yml +191 -0
  89. data/examples/viewtodo.rb +574 -0
  90. data/lib/rbcurse/deprecated/README.markdown +12 -0
  91. data/lib/rbcurse/deprecated/rpad.rb +375 -0
  92. data/lib/rbcurse/deprecated/rscrollpane.rb +512 -0
  93. data/lib/rbcurse/deprecated/rsplitpane.rb +894 -0
  94. data/lib/rbcurse/deprecated/rsplitpane2.rb +1009 -0
  95. data/lib/rbcurse/deprecated/rviewport.rb +204 -0
  96. data/lib/rbcurse/deprecated/widgets/mapper.rb +130 -0
  97. data/lib/rbcurse/deprecated/widgets/rmessagebox.rb +348 -0
  98. data/lib/rbcurse/deprecated/widgets/rtabbedpane.rb +1158 -0
  99. data/lib/rbcurse/deprecated/widgets/rtabbedwindow.rb +167 -0
  100. data/lib/rbcurse/deprecated/widgets/scrollable.rb +301 -0
  101. data/lib/rbcurse/deprecated/widgets/stdscrwindow.rb +309 -0
  102. data/lib/ver/keyboard2.rb +170 -0
  103. data/test/test_rbcurse.rb +0 -0
  104. metadata +131 -9
@@ -0,0 +1,952 @@
1
+ require 'rbcurse/core/util/app'
2
+ require 'fileutils'
3
+ require 'yaml'
4
+ require 'gmail'
5
+ # You need gmail gem. (one of them depends on i18n gem).
6
+ # # stopped working since gmail does not accept UIDS XXX FIXME
7
+ # TODO start putting commands in a popup or menu bar
8
+ # TODO what if i want to hide sidebar and bring it back on later
9
+ # TODO switch mailbox or label on command line, with prompt letter indexing
10
+ # TODO
11
+ # TODO body does not show cc and date from reply_to etc
12
+ # TODO seems like gmail web preloads body so no delay, yet it remains unread XXX
13
+ # TODO: compose a message - what of contacts ? cc bcc
14
+ # TODO: reply.
15
+ # TODOx refresh, perhaps get unread and compare UIDS
16
+ # FIXME: reconnect gave allmain count as inbox count, clicking on lb2 gave nilclass
17
+ # TODOx : x highlight / select if pressing enter, so we know which one is being shwon
18
+ # _ should work with select also. Now we have a kind of mismatch between select
19
+ # and press (spacebar and enter)
20
+ # TODOx : cache envelope and body so not read repeatedly
21
+ # TODOD: C-w C-w should go between current and last. it does left and right
22
+ # TODO: option for only unseen mails
23
+ # FIX column widths so date also showm TODO
24
+ # # reduce width of left
25
+ # TODO: upper right should say NEW if N, also deleted can be D and not removed ??
26
+ # but there's no way to undelete.
27
+ # TODO: handling of packed (munpack) - temporary fix in place
28
+ # TODO : catch connectionreset and relogin : Connection reset by peer (Errno::ECONNRESET)
29
+ # TODO: unread on top, or only unread
30
+ # TODO: offline, download all mails (body too)
31
+ # TODO: select read, unread, by same author, starred, unstarred
32
+
33
+ class OpenedMessage < Struct.new(:uid, :message, :index)
34
+ def set uid, mess, index
35
+ $log.debug "XXX opened got index #{index} " if $log.debug?
36
+ @uid = uid
37
+ @message = mess
38
+ @index = index
39
+ $log.debug "XXX opened got @index #{@index} " if $log.debug?
40
+ end
41
+ end
42
+
43
+ #module RubyCurses
44
+ #class App
45
+ # putting some commands here so we can call from command_line
46
+ # ADD
47
+ def get_commands
48
+ opts = %w{ test testend archive delete markread markunread spam star unstar open header savecontact connect connectas compose refresh }
49
+ current_component = @vim.current_component
50
+ case current_component
51
+ when @lb2
52
+ opts.push *%w{ select nextm prev nextunread prevunread savecontact header }
53
+ when @tv
54
+ opts.push *%w{ saveas reply replyall nextm prev nextunread prevunread munpack }
55
+ end
56
+ opts
57
+ end
58
+ # indices can be array or range
59
+ # can pass one row as array, @lb2.selected_indices or range 20..25
60
+ # . delete
61
+ # .,+20 delete
62
+ # 1,4 archive
63
+ # .,$ select mark etc
64
+ def nextm n=1
65
+ ri = @lb2.real_index
66
+ ri = @opened_message.index
67
+ alert "ri in nil in nextm " unless ri
68
+ return unless ri
69
+ #@lb2.down
70
+ #return if ri == @lb2.real_index
71
+ if ri + n < @lb2.row_count
72
+ #@lb2.current_index += 1
73
+ open_mail(ri+n) #@lb2.real_index
74
+ else
75
+ raw_message "No more messages"
76
+ end
77
+ end
78
+ def prev
79
+ ri = @lb2.real_index
80
+ ri = @opened_message.index
81
+ return unless ri
82
+ #@lb2.up # this moves cursor to lb2
83
+ #return if ri == @lb2.real_index
84
+ if ri > 0
85
+ @lb2.current_index -= 1
86
+ open_mail @lb2.real_index
87
+ else
88
+ say "No previous messages", :color_pair => $prompt_color
89
+ end
90
+ end
91
+
92
+ # fetch body of message and display in textview
93
+ def open_mail index
94
+ @current_opened_index = index
95
+ row = @lb2[index]
96
+ unless row
97
+ say "Invalid row.", :color_pair => $prompt_color
98
+ return
99
+ end
100
+ if index >= 0
101
+ uid = row[UID_OFFSET] # UID_OFFSET=5
102
+ #uid = @message_uids[index]
103
+ #body = @gmail.connection.uid_fetch(uid, "BODY[TEXT]")[0].attr['BODY[TEXT]']
104
+ message_immediate "Fetching body from server ..."
105
+ body = uid_to_message( uid ).body # this uses gmail gem's cache
106
+ body = body.decoded.encode("ASCII-8BIT", :invalid => :replace, :undef => :replace, :replace => "?")
107
+ #@tv.set_content(body.to_s, :WRAP_WORD)
108
+ @current_uid = uid
109
+ @current_message = uid_to_message(uid)
110
+ env = @current_message.envelope
111
+ f = env.from[0]
112
+ t = env.to[0]
113
+ from = "From: #{env.from[0].name.to_s} <#{env.from[0].mailbox.to_s}@#{f.host.to_s}> "
114
+ to = "To: #{env.to[0].name.to_s} <#{env.to[0].mailbox.to_s}@#{t.host.to_s}> "
115
+ str = to
116
+ str << "\n" << from << "\n" << "Date: " << env.date << "\n" << "Subject: " << env.subject << "\n\n"
117
+ str << body
118
+ @tv.set_content(str, :WRAP_WORD)
119
+ @opened_message.set(uid, uid_to_message(uid), index)
120
+ @opened_message.uid = uid
121
+ @opened_message.message = uid_to_message(uid)
122
+ @opened_message.index = index
123
+ @current_body = body
124
+ row[0][0] = " " if row[0][0] == "N"
125
+
126
+ message "Done. "
127
+ @lb2.repaint_required true
128
+ @form.repaint
129
+ end
130
+ end
131
+ # i partial command entered then returns matches
132
+ def _resolve_command opts, cmd
133
+ return cmd if opts.include? cmd
134
+ matches = opts.grep Regexp.new("^#{cmd}")
135
+ end
136
+
137
+ def select which=nil
138
+ unless which
139
+ opts = %w{ all none read unread starred unstarred from subject invert current n}
140
+ which = ask("Select (TAB for options): ", opts) #{ |q| q.default = @previous_command }
141
+ end
142
+ which = which.to_sym
143
+ case which
144
+ when :all
145
+ @lb2.select_all
146
+ when :none
147
+ @lb2.clear_selection
148
+ when :invert
149
+ @lb2.invert_selection
150
+ when :unread
151
+ list = @lb2.list
152
+ list.each_with_index { |row, i|
153
+ if row[0][0]=="N"
154
+ @lb2.add_row_selection_interval(i,i)
155
+ end
156
+ }
157
+ when :read
158
+ list = @lb2.list
159
+ list.each_with_index { |row, i|
160
+ if row[0][0]=="N"
161
+ else
162
+ @lb2.add_row_selection_interval(i,i)
163
+ end
164
+ }
165
+ when :from
166
+ cv = @lb2.current_value
167
+ from = cv[1]
168
+ list = @lb2.list
169
+ list.each_with_index { |row, i|
170
+ if row[1] == from
171
+ @lb2.add_row_selection_interval(i,i)
172
+ end
173
+ }
174
+ when :n
175
+ n = ask("How many? ", Integer) {|q| q.in = 1..100}
176
+ ci = @lb2.current_index - 1 # header_adjustment
177
+ @lb2.add_row_selection_interval ci, ci+n-1
178
+ end
179
+ end
180
+
181
+ # just experimental, we load all mails on clicking a label anyway
182
+ # pick out all new mails in INBOX, compare to unread ids list
183
+ # and only add in those not in that list. Nothing great.
184
+
185
+ def refresh
186
+ #@gmail.inbox.find(:unread) do |email|
187
+ raw_message "Fetching ..."
188
+ ctr = 0
189
+ begin
190
+ @gmail.label("INBOX") do |mailbox|
191
+ unread = mailbox.emails(:unread)
192
+ total = unread.size
193
+ unread.each_with_index do |email, index|
194
+ uid = email.uid
195
+ if !@unreaduids.include? uid
196
+ @unreaduids << uid
197
+ ctr += 1
198
+ env = email.envelope
199
+ row = convert_message_to_row env, uid
200
+ @lb2.insert 0, row
201
+ end
202
+ raw_progress([index, total])
203
+ end
204
+ end
205
+ raw_message "#{ctr} new messages in inbox."
206
+ #refresh_labels
207
+ rescue => ex
208
+ $log.debug( "EXC refresh rescue reached. ")
209
+ print_error ex
210
+ end
211
+
212
+ end
213
+ # one place to write and display exception
214
+ def print_error ex
215
+ if ex
216
+ $log.debug( ex)
217
+ $log.debug(ex.backtrace.join("\n"))
218
+ message "EXCEPTION : #{ex} "
219
+ @message_label.repaint
220
+ @window.refresh
221
+ end
222
+ end
223
+ def convert_message_to_row envelope, uid
224
+ e = envelope
225
+ flag = @unreaduids.include?(uid) ? "N " : " "
226
+ if @starred_uids.include?(uid)
227
+ flag[1]="+"
228
+ end
229
+ date = e.date # .to_s #[5..10]
230
+ date = Date.parse(date).strftime("%b %d")
231
+ # name returns an Array, which crashes sort - therefore to_s, but says String
232
+ from = e.from[0].name.to_s
233
+ from = e.from[0].mailbox.to_s if from == ""
234
+ [ flag, from, e.subject ,date, uid]
235
+ end
236
+
237
+ def compose
238
+ # TODO make a separate screen damn you !
239
+ name = ask("To: ") # choices should be names from contacts
240
+ subject = ask("Subject: ")
241
+ # shell vim from here using temporary file
242
+ body = edit_text nil
243
+ message_immediate "sending message ... "
244
+ if body
245
+ @gmail.deliver do
246
+ to name
247
+ subject subject
248
+ body body
249
+ end
250
+ end
251
+ message "sent message to #{name} "
252
+ end
253
+
254
+ # needs to go into utils
255
+ def edit_text text
256
+ # 2010-06-29 10:24
257
+ require 'fileutils'
258
+ require 'tempfile'
259
+ ed = ENV['EDITOR'] || "vim"
260
+ temp = Tempfile.new "tmp"
261
+ File.open(temp,"w"){ |f| f.write text } if text
262
+ mtime = File.mtime(temp.path)
263
+ suspend() do
264
+ system(ed, temp.path)
265
+ end
266
+
267
+ newmtime = File.mtime(temp.path)
268
+ newstr = nil
269
+ if mtime < newmtime
270
+ # check timestamp, if updated ..
271
+ newstr = File.read(temp)
272
+ else
273
+ #puts "user quit without saving"
274
+ return nil
275
+ end
276
+ return newstr.chomp if newstr
277
+ return nil
278
+ end
279
+ def header
280
+ e = @lb2
281
+ env = get_current_message e
282
+ message_immediate "Fetching header ..."
283
+ header = env.header.to_s
284
+ case header
285
+ when String
286
+ header = header.split "\n"
287
+ end
288
+ view(header)
289
+ end
290
+ def savecontact
291
+ e = @lb2
292
+ env = get_current_message e
293
+ $log.debug "XXX ENV #{env} " if $log.debug?
294
+ name = env.from[0].name
295
+ id = "#{env.from[0].mailbox}@#{env.from[0].host}"
296
+
297
+ obj = nil
298
+ filename = "contacts.yml"
299
+ if File.exists? filename
300
+ obj = YAML::load_file( filename )
301
+ end
302
+ obj ||=[]
303
+ obj << [name, id]
304
+ File.open(filename, 'w' ) do |f|
305
+ f << obj.to_yaml
306
+ end
307
+ message "Written #{name} #{id} to #{filename} "
308
+ end
309
+ def connectas
310
+ user = ask "Emailid: "
311
+ return unless user
312
+ pass = ask("Password", String){ |q| q.echo = '*' }
313
+ return unless pass
314
+ gmail_connect(user, pass)
315
+ end
316
+ def connect
317
+ gmail_connect
318
+ end
319
+ def test
320
+ # creating a scratch window. should be put a textview in it ? or label ?
321
+ require 'rbcurse/core/util/rcommandwindow'
322
+ @layout = { :height => 5, :width => Ncurses.COLS-1, :top => Ncurses.LINES-5, :left => 0 }
323
+ rc = CommandWindow.new nil, :layout => @layout
324
+ w = rc.window
325
+ w.box(0,0)
326
+ w.printstring 1,1, "hello there!", $normalcolor, 'normal'
327
+ #rc.handle_keys
328
+ @rc = rc
329
+ end
330
+ def testend
331
+ @rc.destroy if @rc
332
+ @rc = nil
333
+ end
334
+ def archive
335
+ if @vim.current_component == @tv
336
+ archive_current
337
+ return
338
+ end
339
+ inds, ms = do_selected_rows(@lb2)
340
+ inds = inds.sort.reverse
341
+ inds.each { |e| @lb2.delete_at e; @messages.delete_at e }
342
+ @lb2.clear_selection
343
+ say " #{inds.size} messages archived"
344
+ Thread.new {
345
+ ms.each { |m|
346
+ m.archive!
347
+ }
348
+ }
349
+ end
350
+ # delete current mail, should be called from tv for opened row
351
+ # FIXME what if delete repeatedly. what if no next should be check UID
352
+ # when we fetch row
353
+ def delete_current
354
+ do_with_opened do |ri, message|
355
+ @lb2.delete_at ri
356
+ Thread.new { message.delete! }
357
+ end
358
+ # if we've delete then automatically next falls into place, no need to
359
+ # add one to idnex
360
+ nextm 0
361
+ end
362
+ def archive_current
363
+ do_with_opened do |ri, message|
364
+ @lb2.delete_at ri
365
+ Thread.new { message.archive! }
366
+ end
367
+ nextm 0
368
+
369
+ end
370
+ def do_with_opened
371
+ if @vim.current_component != @tv
372
+ say "not on tv. please open a mail and then delete"
373
+ return
374
+ end
375
+ ri = @lb2.real_index
376
+ ri = @opened_message.index
377
+ return if ri.nil? || ri < 1 # header_adjust
378
+ row = @lb2[ri]
379
+ rowuid = row[UID_OFFSET]
380
+ if @opened_message.uid != rowuid
381
+ alert "something wrong, uid not matching"
382
+ end
383
+ message = get_current_message
384
+ return unless message
385
+ yield ri, message if block_given?
386
+ end
387
+ def delete
388
+ if @vim.current_component == @tv
389
+ delete_current
390
+ return
391
+ end
392
+ e = @lb2
393
+ aproc = lambda {|m| m.delete! }
394
+ inds, ms = for_selected_rows(e, aproc) { |e| @lb2.delete_at e; @messages.delete_at e }
395
+ @lb2.clear_selection
396
+ say " #{inds.size} messages deleted" if inds
397
+ end
398
+ def saveas *args
399
+ @tv.saveas *args
400
+ end
401
+ def remove_current_label m
402
+ label = @current_label
403
+ m.remove_label! label
404
+ end
405
+ # return message object for a UID
406
+ # m.envelope
407
+ # m.header # < take a little time
408
+ def uid_to_message uid
409
+ m = @uid_message[uid]
410
+ end
411
+ # calls block for selected rows
412
+ def do_selected_rows(w) # :yield: row, msg
413
+ indices = []
414
+ messages = []
415
+ w.selected_rows.each { |row|
416
+ uid = w[row][UID_OFFSET] # UID_OFFSET
417
+ message = uid_to_message uid
418
+ next unless message
419
+ yield row, message if block_given?
420
+ indices << row
421
+ messages << message
422
+ }
423
+ return indices, messages
424
+ end
425
+ def get_current_uid w=@lb2
426
+ row = w.current_value
427
+ uid = row[UID_OFFSET] # UID_OFFSET
428
+ end
429
+ def get_current_message w=@lb2
430
+ uid = get_current_uid w
431
+ message = uid_to_message uid
432
+ end
433
+ def for_rows(indices, messageproc=nil)
434
+ case indices
435
+ when Integer
436
+ indices = [indices]
437
+ when Range
438
+ indices = indices.to_a
439
+ when :selected
440
+ indices = w.selected_rows
441
+ end
442
+ w = @lb2
443
+ messages = []
444
+ indices = indices.sort.reverse
445
+ indices.each { |row|
446
+ uid = w[row][UID_OFFSET] # UID_OFFSET
447
+ message = uid_to_message uid
448
+ next unless message
449
+ messages << message
450
+ }
451
+ return false unless indices
452
+ indices.each { |i| yield i if block_given? }
453
+ if messageproc
454
+ thr = Thread.new{
455
+ begin
456
+ messages.each { |m| messageproc.call(m) }
457
+ rescue => ex
458
+ $log.debug( "EXC for_selected_row rescue reached. ")
459
+ if ex
460
+ $log.debug( ex)
461
+ $log.debug(ex.backtrace.join("\n"))
462
+ message "EXCEPTION IN THREAD: #{ex} "
463
+ @message_label.repaint
464
+ @window.refresh
465
+ end
466
+ end
467
+ }
468
+ thr.abort_on_exception = true
469
+ end
470
+ return indices, messages
471
+ end
472
+ # for each selected row, execute the yield row indices to block
473
+ # typically for deleting from table, or updating visual status.
474
+ # Also call messageproc in a thread for each message since imap
475
+ # operations take a little time.
476
+ # indices are given in reverse order, so delete of rows in table
477
+ # works correctly.
478
+ #@return [false] if no selected rows
479
+ #@return [Array<Fixnum>, Array<messages>] visual indices in listbox, and related messages (envelopes)
480
+ def for_selected_rows(w, messageproc=nil)
481
+ indices = []
482
+ messages = []
483
+ w.selected_rows.each { |row|
484
+ uid = w[row][UID_OFFSET] # UID_OFFSET
485
+ message = uid_to_message uid
486
+ next unless message
487
+ indices << row
488
+ messages << message
489
+ }
490
+ return false unless indices
491
+ indices = indices.sort.reverse
492
+ indices.each { |i| yield i if block_given? }
493
+ if messageproc
494
+ thr = Thread.new{
495
+ begin
496
+ messages.each { |m| messageproc.call(m) }
497
+ rescue => ex
498
+ $log.debug( "EXC for_selected_row rescue reached. ")
499
+ if ex
500
+ $log.debug( ex)
501
+ $log.debug(ex.backtrace.join("\n"))
502
+ message "EXCEPTION IN THREAD: #{ex} "
503
+ @message_label.repaint
504
+ @window.refresh
505
+ end
506
+ end
507
+ }
508
+ thr.abort_on_exception = true
509
+ end
510
+ return indices, messages
511
+ end
512
+ # fetch envelopes for a label name
513
+ # and populates the right table
514
+ def get_envelopes text
515
+ @current_label = text
516
+ $break_fetch = false
517
+ #@message_uids = []
518
+ @messages = [] # hopefully unused
519
+ #Thread.new {
520
+ begin
521
+ ctr = 0
522
+ @gmail.label(text) do |mailbox|
523
+ # TODO. praps a progress bar also.
524
+ unreaduids = []
525
+ unread = mailbox.emails(:unread)
526
+ urc = unread.size
527
+ dispstr = " #{text} : unread #{urc} "
528
+ message_immediate dispstr
529
+ $unread_hash[text] = urc
530
+ allmails = mailbox.emails(:read)
531
+ allmails.insert 0, *unread
532
+ total = allmails.size
533
+ dispstr << " total: #{total} "
534
+ unread.each do |email|
535
+ unreaduids << email.uid
536
+ end
537
+ @unreaduids = unreaduids
538
+ dispstr << " getting UIDs .."
539
+ message_immediate dispstr
540
+ uids = []
541
+ allmails.each do |email|
542
+ uids << email.uid
543
+ @uid_message[email.uid] = email
544
+ end
545
+ message_immediate "getting envelopes. unread: #{urc} total: #{total} "
546
+ raw_progress 0.25
547
+
548
+ envelopes = @gmail.connection.uid_fetch(uids, "(UID ENVELOPE)")
549
+ # may need to reverse sort this !! TODO
550
+ # reversing means we've lost the link to UID !!! so we redo the list
551
+ return unless envelopes
552
+ lines = []
553
+ envelopes.reverse.each_with_index { |ee, index|
554
+ raw_progress([index+1, total])
555
+ e = ee.attr["ENVELOPE"]
556
+ uid = ee.attr["UID"]
557
+ #@message_uids << uid UNUSED
558
+ flag = unreaduids.include?(uid) ? "N " : " "
559
+ if @starred_uids.include?(uid)
560
+ flag[1]="+"
561
+ end
562
+ date = e.date # .to_s #[5..10]
563
+ date = Date.parse(date).strftime("%b %d")
564
+ #$log.debug "name: XXX #{e.from[0].name} "
565
+ #$log.debug "name: XXX #{e.from[0].class} " unless e.from[0].nil?
566
+ # name returns an Array, which crashes sort - therefore to_s, but says String
567
+ from = e.from[0].name.to_s # why blank some times FIXME
568
+ from = e.from[0].mailbox.to_s if from == ""
569
+ #@lb2.append([ flag, ctr+1 , from, e.subject ,date])
570
+ #lines << [ flag, ctr+1 , from, e.subject ,date, uid]
571
+ lines << [ flag, from, e.subject ,date, uid]
572
+ ctr+=1
573
+ @messages << e
574
+ break if ctr >= @max_to_read
575
+ break if $break_fetch # can only happen in threaded mode
576
+ }
577
+ @lb2.estimate_column_widths=true # this sort of fails if we keep pushing a row at a time
578
+ @lb2.set_content lines
579
+ #message " #{text} showing #{ctr} of #{total} messages"
580
+ #@message_label.repaint
581
+ @form.repaint
582
+ end
583
+ rescue => ex
584
+ $log.debug( "EXC thread.rb rescue reached. ")
585
+ if ex
586
+ $log.debug( ex)
587
+ $log.debug(ex.backtrace.join("\n"))
588
+ message "EXCEPTION IN THREAD: #{ex} Reconnect using M-c"
589
+ @message_label.repaint
590
+ @window.refresh
591
+ gmail_connect # this should only happen in imap error not just any
592
+ end
593
+ end
594
+ #}
595
+ end
596
+ def get_starred gmail
597
+ @starred_uids = []
598
+ starred = @gmail.mailbox("[Gmail]/Starred")
599
+ starred.mails.each do |email|
600
+ @starred_uids << email.uid
601
+ end
602
+ $log.debug "XXX got starred #{@starred_uids.size} " if $log.debug?
603
+ end
604
+ # connect to gmail,
605
+ # but what if i want to change user - then we need to clear hashes.
606
+ def gmail_connect username=ENV['GMAIL_USER']+"@gmail.com", pass=ENV['GMAIL_PASS']
607
+ @gmail = Gmail.connect!(username, pass)
608
+ message_immediate "Connected to gmail, fetching labels ... "
609
+ @labels = @gmail.labels.all
610
+ message_immediate "Fetched labels. Click on a label. "
611
+ get_starred @gmail
612
+ @dirs.list @labels
613
+ @lb2.remove_all
614
+ @tv.remove_all
615
+ @form.repaint
616
+ # pull in inbox contents
617
+ # pull in unread for each label
618
+ #Thread.new { refresh_labels }
619
+ refresh_labels
620
+ @vim.focus @dirs
621
+ # place cursor on @dir since lb2 empty FIXME
622
+ end
623
+ # fetches labels and refreshes the unread counts
624
+ # I suspect there are issues here if this is in background, and someone presses enter
625
+ # on a label. he sees only unread -data inconsistency/race condition
626
+ def refresh_labels
627
+ #message_immediate " inside refresh labels "
628
+ raw_message "Getting label information..."
629
+ @labels ||= @gmail.labels.all
630
+ total = @labels.size
631
+ @labels.each_with_index { |text, index|
632
+ next if text == "[GMAIL]"
633
+ begin
634
+ @gmail.label(text) do |mailbox|
635
+ unread = mailbox.emails(:unread) # maybe this causes an issue internally
636
+ urc = unread.size
637
+ #message_immediate " mailbox #{text} has #{urc} unread "
638
+ $unread_hash[text] = urc
639
+ raw_progress([index+1, total])
640
+ end
641
+ rescue => ex
642
+ $log.debug " refresh_labels :: ERROR in mailbox #{text} ... #{ex}" if $log.debug?
643
+ next
644
+ end
645
+ }
646
+ @dirs.repaint_required(true)
647
+ message_immediate " Ready"
648
+ end
649
+ #end
650
+ #end
651
+
652
+ # START start
653
+ UID_OFFSET = 4
654
+ App.new do
655
+ #begin
656
+ @opened_message = OpenedMessage.new
657
+ ht = 24
658
+ @max_to_read = 100
659
+ @messages = nil # hopefully unused
660
+ @labels = nil
661
+ @current_label = nil
662
+ @message_uids = nil # uids of messages being displayed in @lb2 so as to get body
663
+ @starred_uids = nil
664
+ @uid_message = {} # map UID to a message
665
+ @unreaduids = [] # current labels unread
666
+ $unread_hash = {}
667
+ $message_hash = {}
668
+ @tv = nil
669
+ @current_body = nil
670
+ username = ENV['GMAIL_USER']+"@gmail.com"
671
+ pass = ENV['GMAIL_PASS']
672
+ @default_mailbox = "INBOX"
673
+ @gmail = nil
674
+ borderattrib = :reverse
675
+ @header = app_header "rbcurse #{Rbcurse::VERSION}", :text_center => "Yet Another Gmail Client that sucks", :text_right =>"", :color => :black, :bgcolor => :white#, :attr => Ncurses::A_BLINK
676
+ message "Press F1 to exit ...................................................."
677
+
678
+
679
+ stack :margin_top => 1, :margin => 0, :width => :EXPAND do
680
+
681
+ model = [" Fetching ..."]
682
+ # todo, get unread too and update, do that at some interval
683
+
684
+ @vim = master_detail :width => :EXPAND, :weight => 0.15 # TODO i want to change width of left container
685
+ # labels list on left
686
+ @dirs = listbox :list => model, :height => ht, :border_attrib => borderattrib, :suppress_borders => true
687
+ @dirs.one_key_selection = false
688
+
689
+ # we override/open instance so as to only print basename. Also, print unread count
690
+ def @dirs.convert_value_to_text(text, crow)
691
+ str = text.dup
692
+ if $unread_hash.has_key?(str)
693
+ str << " (#{$unread_hash[str]})"
694
+ else
695
+ str
696
+ end
697
+ end
698
+ @vim.set_left_component @dirs #, 0.25 # FIXME not having impact
699
+
700
+
701
+ #@mails = []
702
+ headings = %w{ __ From Subject Date UID }
703
+ @lb2 = tabular_widget :suppress_borders => true
704
+ # TODO set column widths since we are pushing one at a time.
705
+ @lb2.columns = headings
706
+ #@lb2.column_align 1, :right # earlier numbering as in alpine
707
+ #@lb2.column_align 0, :right
708
+ @lb2.column_hidden UID_OFFSET, true
709
+ @lb2.header_fgcolor :white
710
+ @lb2.header_bgcolor :cyan
711
+ @vim.set_right_top_component @lb2
712
+ Thread.new {
713
+ begin
714
+ @gmail = Gmail.connect!(username, pass)
715
+ message_immediate "Connected to gmail, fetching labels ... "
716
+ @labels = @gmail.labels.all
717
+ message_immediate "Fetched #{@labels.count} labels. Click on a label. "
718
+ @dirs.list @labels
719
+ @form.repaint
720
+ get_starred @gmail
721
+ message_immediate "Fetching #{@default_mailbox} " if @default_mailbox
722
+ get_envelopes @default_mailbox if @default_mailbox
723
+ #Thread.new { refresh_labels }
724
+ rescue => ex
725
+ if ex
726
+ $log.debug( ex)
727
+ $log.debug(ex.backtrace.join("\n"))
728
+ message "EXCEPTION IN THREAD: #{ex} "
729
+ @message_label.repaint
730
+ @window.refresh
731
+ end
732
+ end
733
+ }
734
+ @dirs.bind :PRESS do |e|
735
+ # TODO = methodize this so i can call it on startup
736
+ text = e.text # can this change if user goes down in dir2 YES
737
+ ci = e.source.current_index
738
+ @dirs.add_row_selection_interval ci, ci # show selected, this should happen on fire auto
739
+ message_immediate "Wait a few seconds ..."
740
+ # don't allow if alreadt inside this, since thread - or only allow one thread FIXME
741
+ # # TODO NOW WE NEED TO CACHE SINC we are not using gmail gem cache
742
+ @lines = []
743
+ @messages = [] # hopefully unused
744
+ #@lb2.remove_all
745
+ @lb2.estimate_column_widths=true # this sort of fails if we keep pushing a row at a time
746
+ # and repainting
747
+ get_envelopes text
748
+ end
749
+ # will only work in Thread mode
750
+ @dirs.bind_key(?q) { $break_fetch = true }
751
+ @dirs.bind_key(27) { $break_fetch = true }
752
+ @form.bind_key(?\M-p){
753
+ require 'live_console'
754
+
755
+ lc = LiveConsole.new :socket, :port => 4000, :bind => self.get_binding
756
+ lc.start # Starts the LiveConsole thread
757
+ alert "started console on 4000 #{self} "
758
+ # you would connect using "nc localhost 4000"
759
+ # if you use pp then it shows here too and mucks the screen.
760
+ # i think it writes on STDSCR - do not use pp and puts, just enter the variable
761
+ }
762
+ @form.bind_key(?\M-y){
763
+ # TODO previous command to be default
764
+ opts = %w{ test testend archive delete markread markunread spam star unstar open header savecontact connect connectas compose refresh }
765
+ current_component = @vim.current_component
766
+ case current_component
767
+ when @lb2
768
+ opts.push *%w{ select nextm prev nextunread prevunread savecontact header }
769
+ when @tv
770
+ opts.push *%w{ saveas reply replyall nextm prev nextunread prevunread munpack }
771
+ end
772
+ cmd = ask("Command: ", opts){ |q| q.default = @previous_command }
773
+ if cmd == ""
774
+ else
775
+ cmdline = cmd.split
776
+ cmd = cmdline.shift
777
+ # check if command is a substring of a larger command
778
+ if !opts.include?(cmd)
779
+ rcmd = _resolve_command(opts, cmd) if !opts.include?(cmd)
780
+ if rcmd.size == 1
781
+ cmd = rcmd.first
782
+ else
783
+ alert "Cannot resolve #{cmd}. Matches are: #{rcmd} "
784
+ end
785
+ end
786
+ if respond_to?(cmd, true)
787
+ @previous_command = cmd
788
+ raw_message "calling #{cmd} "
789
+ begin
790
+ send cmd, *cmdline
791
+ rescue => exc
792
+ $log.debug "ERR EXC: send throwing an exception now. Duh. IMAP keeps crashing haha !! #{exc} " if $log.debug?
793
+ print_error exc
794
+ end
795
+ else
796
+ say("Command [#{cmd}] not supported by #{self.class} ")
797
+ end
798
+ end
799
+ }
800
+
801
+ @form.bind_key(?\M-m){
802
+ @max_to_read = ask("How many mails to retrieve? ", Integer) { |q| q.in = 1..1000 }
803
+ }
804
+ # write file to disk so as to munpack it
805
+ @form.bind_key(?\M-s){
806
+ if @current_body
807
+ File.open("message.txt", 'w') {|f| f.write(@current_body) }
808
+ message_immediate "Written body as message.txt. You may use munpack on file"
809
+ end
810
+ }
811
+ @form.bind_key(?\M-c){
812
+ gmail_connect
813
+ }
814
+ @form.bind_key(?\M-C){
815
+ connectas
816
+ }
817
+ @lb2.bind :PRESS do |e|
818
+ case @lb2
819
+ when RubyCurses::TabularWidget
820
+ if e.action_command == :header
821
+ # now does sorting on multiple keys
822
+ else
823
+ ci = e.source.current_index # this should check what first data index is
824
+ index = ci - 1
825
+ open_mail index
826
+ #@current_opened_index = index
827
+ #row = @lb2[index]
828
+ #uid = row[UID_OFFSET] # UID_OFFSET=5
829
+ #if index >= 0
830
+ ##uid = @message_uids[index]
831
+ ##body = @gmail.connection.uid_fetch(uid, "BODY[TEXT]")[0].attr['BODY[TEXT]']
832
+ #message_immediate "Fetching body from server ..."
833
+ #body = uid_to_message( uid ).body # this uses gmail gem's cache
834
+ #body = body.decoded.encode("ASCII-8BIT", :invalid => :replace, :undef => :replace, :replace => "?")
835
+ #@tv.set_content(body, :WRAP_WORD)
836
+ ##@tv.set_content(body.to_s, :WRAP_WORD)
837
+ #@current_uid = uid
838
+ #@current_message = uid_to_message(uid)
839
+ #@opened_message.set(uid, uid_to_message(uid), index)
840
+ #@opened_message.uid = uid
841
+ #@opened_message.message = uid_to_message(uid)
842
+ #@opened_message.index = index
843
+ #@current_body = body
844
+ #row[0][0] = " " if row[0][0] == "N"
845
+ #$log.debug "XXX opened_message:: #{@opened_message} " if $log.debug?
846
+ #$log.debug "XXX opened_message index:: #{@opened_message.index}, #{index} " if $log.debug?
847
+
848
+ #message "Done. "
849
+ #@lb2.repaint_required true
850
+ #@form.repaint
851
+
852
+ ##@tv.set_content(@messages[index].body.to_s, :WRAP_WORD)
853
+ #end
854
+ end
855
+ else
856
+ @tv.set_content(@messages[e.source.current_index].body, :WRAP_WORD)
857
+ end
858
+ end
859
+ @lb2.bind :ENTER_ROW do |e|
860
+ @header.text_right "Row #{e.current_index} of #{@messages.size} "
861
+ end
862
+ @lb2.bind_key(?\M-a) do |e|
863
+ env = get_current_message e
864
+ $log.debug "XXX ENV #{env} " if $log.debug?
865
+ alert " #{env.from[0].name} :: #{env.from[0].mailbox}@#{env.from[0].host} "
866
+ $log.debug "XXX ENV HEADER #{env.header} " if $log.debug?
867
+ end
868
+ @lb2.bind_key(?U){ |e|
869
+ aproc = lambda {|m| m.mark(:unread) }
870
+ for_selected_rows(e, aproc) { |i|
871
+ row = @lb2[i]
872
+ row[0][0] = "N" if row[0][0] == " "
873
+ }
874
+ }
875
+ @lb2.bind_key(?I){ |e|
876
+ aproc = lambda {|m| m.mark(:read) }
877
+ for_selected_rows(e, aproc) { |i|
878
+ row = @lb2[i]
879
+ row[0][0] = " " if row[0][0] == "N"
880
+ }
881
+ }
882
+ # we have no way of knowing which ones are starred or unstarred, so can't show.
883
+ @lb2.bind_key(?s){ |e|
884
+ # looks like star and unstar don't work
885
+ raw_message "called star"
886
+ aproc = lambda {|m| m.star! }
887
+ for_selected_rows(e, aproc) { |i|
888
+ row = @lb2[i]
889
+ row[0][1] = "+" #if row[0][1] == " "
890
+ raw_message "called star for #{i} #{row[2]} "
891
+ }
892
+ }
893
+ @lb2.bind_key(?S){ |e|
894
+ aproc = lambda {|m| m.unstar! }
895
+ for_selected_rows(e, aproc) { |i|
896
+ row = @lb2[i]
897
+ row[0][1] = " " #if row[0][1] == "#"
898
+ }
899
+ }
900
+ # remove current label
901
+ @lb2.bind_key(?X){ |e|
902
+ label = @current_label
903
+ return unless label
904
+ aproc = lambda {|m| remove_current_label(m) }
905
+ inds, ms = for_selected_rows(e, aproc) { |e| @lb2.delete_at e; @messages.delete_at e }
906
+ @lb2.clear_selection
907
+ }
908
+ @lb2.bind_key(?#){ |e|
909
+ aproc = lambda {|m| m.delete! }
910
+ inds, ms = for_selected_rows(e, aproc) { |e| @lb2.delete_at e; @messages.delete_at e }
911
+ @lb2.clear_selection
912
+ }
913
+ @lb2.bind_key(?!){ |e|
914
+ aproc = lambda {|m| m.spam! }
915
+ inds, ms = for_selected_rows(e, aproc) { |e| @lb2.delete_at e; @messages.delete_at e }
916
+ @lb2.clear_selection
917
+ }
918
+ # archive
919
+ # this way of defining does not allow user to reassign this method to a key.
920
+ # we should put into a method in a class, so user can reassign
921
+ @lb2.bind_key(?\e){ |e|
922
+ inds, ms = do_selected_rows(e)
923
+ inds = inds.sort.reverse
924
+ inds.each { |e| @lb2.delete_at e; @messages.delete_at e }
925
+ @lb2.clear_selection
926
+ Thread.new {
927
+ ms.each { |m|
928
+ m.archive!
929
+ }
930
+ }
931
+
932
+ }
933
+ @lb2.bind_key(?\M-u){ @lb2.clear_selection }
934
+
935
+ @tv = @vim.set_right_bottom_component "Email body comes here. "
936
+ @tv.bind_key(?\M-m){ o = @tv.pipe_output('munpack', @current_body)
937
+ $log.debug "munpack returned #{o.size} " if $log.debug?
938
+ newfile = o.last.split(" ").first
939
+ $log.debug "munpack file #{newfile} " if $log.debug?
940
+ o = File.read(newfile) if File.exists?(newfile)
941
+ @tv.set_content o
942
+ # this leaves a file in current directory
943
+ }
944
+ @tv.bind_key(?\M-A) { |s| @tv.saveas }
945
+ @tv.suppress_borders true
946
+ @tv.border_attrib = borderattrib
947
+ end # stack
948
+ #ensure
949
+ #$log.debug "XX ENSURE !!! " if $log.debug?
950
+ #gmail.logout
951
+ #end
952
+ end # app