rbcurse 1.5.0 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
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