sup 0.8.1 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/CONTRIBUTORS +13 -6
- data/History.txt +19 -0
- data/ReleaseNotes +35 -0
- data/bin/sup +82 -77
- data/bin/sup-add +7 -7
- data/bin/sup-config +104 -85
- data/bin/sup-dump +4 -5
- data/bin/sup-recover-sources +9 -10
- data/bin/sup-sync +121 -100
- data/bin/sup-sync-back +18 -15
- data/bin/sup-tweak-labels +24 -21
- data/lib/sup.rb +53 -33
- data/lib/sup/account.rb +0 -2
- data/lib/sup/buffer.rb +47 -22
- data/lib/sup/colormap.rb +6 -6
- data/lib/sup/contact.rb +0 -2
- data/lib/sup/crypto.rb +34 -23
- data/lib/sup/draft.rb +6 -14
- data/lib/sup/ferret_index.rb +471 -0
- data/lib/sup/hook.rb +30 -43
- data/lib/sup/hook.rb.BACKUP.8625.rb +158 -0
- data/lib/sup/hook.rb.BACKUP.8681.rb +158 -0
- data/lib/sup/hook.rb.BASE.8625.rb +155 -0
- data/lib/sup/hook.rb.BASE.8681.rb +155 -0
- data/lib/sup/hook.rb.LOCAL.8625.rb +142 -0
- data/lib/sup/hook.rb.LOCAL.8681.rb +142 -0
- data/lib/sup/hook.rb.REMOTE.8625.rb +145 -0
- data/lib/sup/hook.rb.REMOTE.8681.rb +145 -0
- data/lib/sup/imap.rb +18 -8
- data/lib/sup/index.rb +70 -528
- data/lib/sup/interactive-lock.rb +74 -0
- data/lib/sup/keymap.rb +26 -26
- data/lib/sup/label.rb +2 -4
- data/lib/sup/logger.rb +54 -35
- data/lib/sup/maildir.rb +41 -6
- data/lib/sup/mbox.rb +1 -1
- data/lib/sup/mbox/loader.rb +18 -6
- data/lib/sup/mbox/ssh-file.rb +1 -7
- data/lib/sup/message-chunks.rb +36 -23
- data/lib/sup/message.rb +126 -46
- data/lib/sup/mode.rb +3 -2
- data/lib/sup/modes/console-mode.rb +108 -0
- data/lib/sup/modes/edit-message-mode.rb +15 -5
- data/lib/sup/modes/inbox-mode.rb +2 -4
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/line-cursor-mode.rb +18 -18
- data/lib/sup/modes/log-mode.rb +29 -16
- data/lib/sup/modes/poll-mode.rb +7 -9
- data/lib/sup/modes/reply-mode.rb +5 -3
- data/lib/sup/modes/scroll-mode.rb +2 -2
- data/lib/sup/modes/search-results-mode.rb +9 -11
- data/lib/sup/modes/text-mode.rb +2 -2
- data/lib/sup/modes/thread-index-mode.rb +26 -16
- data/lib/sup/modes/thread-view-mode.rb +84 -39
- data/lib/sup/person.rb +6 -8
- data/lib/sup/poll.rb +46 -47
- data/lib/sup/rfc2047.rb +1 -5
- data/lib/sup/sent.rb +27 -20
- data/lib/sup/source.rb +90 -13
- data/lib/sup/textfield.rb +4 -4
- data/lib/sup/thread.rb +15 -13
- data/lib/sup/undo.rb +0 -1
- data/lib/sup/update.rb +0 -1
- data/lib/sup/util.rb +51 -43
- data/lib/sup/xapian_index.rb +566 -0
- metadata +57 -46
- data/lib/sup/suicide.rb +0 -36
data/lib/sup/modes/text-mode.rb
CHANGED
@@ -14,7 +14,7 @@ class TextMode < ScrollMode
|
|
14
14
|
buffer.mark_dirty if buffer
|
15
15
|
super()
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def save_to_disk
|
19
19
|
fn = BufferManager.ask_for_filename :filename, "Save to file: ", @filename
|
20
20
|
save_to_file(fn) { |f| f.puts text } if fn
|
@@ -50,7 +50,7 @@ class TextMode < ScrollMode
|
|
50
50
|
@lines << @text.length
|
51
51
|
if buffer
|
52
52
|
ensure_mode_validity
|
53
|
-
buffer.mark_dirty
|
53
|
+
buffer.mark_dirty
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Redwood
|
2
4
|
|
3
5
|
## subclasses should implement:
|
@@ -38,6 +40,7 @@ EOS
|
|
38
40
|
k.add :save, "Save changes now", '$'
|
39
41
|
k.add :jump_to_next_new, "Jump to next new thread", :tab
|
40
42
|
k.add :reply, "Reply to latest message in a thread", 'r'
|
43
|
+
k.add :reply_all, "Reply to all participants of the latest message in a thread", 'G'
|
41
44
|
k.add :forward, "Forward latest message in a thread", 'f'
|
42
45
|
k.add :toggle_tagged, "Tag/untag selected thread", 't'
|
43
46
|
k.add :toggle_tagged_all, "Tag/untag all threads", 'T'
|
@@ -74,8 +77,7 @@ EOS
|
|
74
77
|
@last_load_more_size = nil
|
75
78
|
to_load_more do |size|
|
76
79
|
next if @last_load_more_size == 0
|
77
|
-
load_threads :num =>
|
78
|
-
load_threads :num => (size - 1),
|
80
|
+
load_threads :num => size,
|
79
81
|
:when_done => lambda { |num| @last_load_more_size = num }
|
80
82
|
end
|
81
83
|
end
|
@@ -109,7 +111,7 @@ EOS
|
|
109
111
|
mode = ThreadViewMode.new t, @hidden_labels, self
|
110
112
|
BufferManager.spawn t.subj, mode
|
111
113
|
BufferManager.draw_screen
|
112
|
-
mode.jump_to_first_open
|
114
|
+
mode.jump_to_first_open
|
113
115
|
BufferManager.draw_screen # lame TODO: make this unnecessary
|
114
116
|
## the first draw_screen is needed before topline and botline
|
115
117
|
## are set, and the second to show the cursor having moved
|
@@ -475,7 +477,7 @@ EOS
|
|
475
477
|
BufferManager.say("Saving threads...") do |say_id|
|
476
478
|
dirty_threads.each_with_index do |t, i|
|
477
479
|
BufferManager.say "Saving modified thread #{i + 1} of #{dirty_threads.length}...", say_id
|
478
|
-
t.
|
480
|
+
t.save_state Index
|
479
481
|
end
|
480
482
|
end
|
481
483
|
end
|
@@ -531,9 +533,9 @@ EOS
|
|
531
533
|
keepl, modifyl = thread.labels.partition { |t| speciall.member? t }
|
532
534
|
|
533
535
|
user_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", modifyl, @hidden_labels
|
534
|
-
|
535
536
|
return unless user_labels
|
536
|
-
|
537
|
+
|
538
|
+
thread.labels = Set.new(keepl) + user_labels
|
537
539
|
user_labels.each { |l| LabelManager << l }
|
538
540
|
update_text_for_line curpos
|
539
541
|
|
@@ -568,6 +570,7 @@ EOS
|
|
568
570
|
LabelManager << l
|
569
571
|
end
|
570
572
|
end
|
573
|
+
UpdateManager.relay self, :labeled, t.first
|
571
574
|
end
|
572
575
|
|
573
576
|
regen_text
|
@@ -581,15 +584,17 @@ EOS
|
|
581
584
|
end
|
582
585
|
end
|
583
586
|
|
584
|
-
def reply
|
587
|
+
def reply type_arg=nil
|
585
588
|
t = cursor_thread or return
|
586
589
|
m = t.latest_message
|
587
590
|
return if m.nil? # probably won't happen
|
588
591
|
m.load_from_source!
|
589
|
-
mode = ReplyMode.new m
|
592
|
+
mode = ReplyMode.new m, type_arg
|
590
593
|
BufferManager.spawn "Reply to #{m.subj}", mode
|
591
594
|
end
|
592
595
|
|
596
|
+
def reply_all; reply :all; end
|
597
|
+
|
593
598
|
def forward
|
594
599
|
t = cursor_thread or return
|
595
600
|
m = t.latest_message
|
@@ -624,6 +629,7 @@ EOS
|
|
624
629
|
BufferManager.draw_screen
|
625
630
|
last_update = Time.now
|
626
631
|
end
|
632
|
+
::Thread.pass
|
627
633
|
break if @interrupt_search
|
628
634
|
end
|
629
635
|
@ts.threads.each { |th| th.labels.each { |l| LabelManager << l } }
|
@@ -756,10 +762,12 @@ protected
|
|
756
762
|
|
757
763
|
def authors; map { |m, *o| m.from if m }.compact.uniq; end
|
758
764
|
|
759
|
-
def author_names_and_newness_for_thread t
|
765
|
+
def author_names_and_newness_for_thread t, limit=nil
|
760
766
|
new = {}
|
761
|
-
authors =
|
767
|
+
authors = Set.new
|
768
|
+
t.each do |m, *o|
|
762
769
|
next unless m
|
770
|
+
break if limit and authors.size >= limit
|
763
771
|
|
764
772
|
name =
|
765
773
|
if AccountManager.is_account?(m.from)
|
@@ -771,12 +779,13 @@ protected
|
|
771
779
|
end
|
772
780
|
|
773
781
|
new[name] ||= m.has_label?(:unread)
|
774
|
-
name
|
782
|
+
authors << name
|
775
783
|
end
|
776
784
|
|
777
|
-
authors.
|
785
|
+
authors.to_a.map { |a| [a, new[a]] }
|
778
786
|
end
|
779
787
|
|
788
|
+
AUTHOR_LIMIT = 5
|
780
789
|
def text_for_thread_at line
|
781
790
|
t, size_widget = @mutex.synchronize { [@threads[line], @size_widgets[line]] }
|
782
791
|
|
@@ -786,7 +795,7 @@ protected
|
|
786
795
|
|
787
796
|
## format the from column
|
788
797
|
cur_width = 0
|
789
|
-
ann = author_names_and_newness_for_thread t
|
798
|
+
ann = author_names_and_newness_for_thread t, AUTHOR_LIMIT
|
790
799
|
from = []
|
791
800
|
ann.each_with_index do |(name, newness), i|
|
792
801
|
break if cur_width >= from_width
|
@@ -842,10 +851,11 @@ protected
|
|
842
851
|
[subj_color, size_widget_text],
|
843
852
|
[:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
|
844
853
|
[:to_me_color, dp ? ">" : (p ? '+' : " ")],
|
845
|
-
[subj_color, t.subj + (t.subj.empty? ? "" : " ")],
|
846
854
|
] +
|
847
|
-
(t.labels - @hidden_labels).map { |label| [:label_color, "
|
848
|
-
[
|
855
|
+
(t.labels - @hidden_labels).map { |label| [:label_color, "#{label} "] } +
|
856
|
+
[
|
857
|
+
[subj_color, t.subj + (t.subj.empty? ? "" : " ")],
|
858
|
+
[:snippet_color, snippet],
|
849
859
|
]
|
850
860
|
end
|
851
861
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'open3'
|
2
1
|
module Redwood
|
3
2
|
|
4
3
|
class ThreadViewMode < LineCursorMode
|
@@ -24,6 +23,18 @@ Return value:
|
|
24
23
|
None. The variable 'headers' should be modified in place.
|
25
24
|
EOS
|
26
25
|
|
26
|
+
HookManager.register "bounce-command", <<EOS
|
27
|
+
Determines the command used to bounce a message.
|
28
|
+
Variables:
|
29
|
+
from: The From header of the message being bounced
|
30
|
+
(eg: likely _not_ your address).
|
31
|
+
to: The addresses you asked the message to be bounced to as an array.
|
32
|
+
Return value:
|
33
|
+
A string representing the command to pipe the mail into. This
|
34
|
+
should include the entire command except for the destination addresses,
|
35
|
+
which will be appended by sup.
|
36
|
+
EOS
|
37
|
+
|
27
38
|
register_keymap do |k|
|
28
39
|
k.add :toggle_detailed_header, "Toggle detailed header", 'h'
|
29
40
|
k.add :show_header, "Show full message header", 'H'
|
@@ -41,7 +52,9 @@ EOS
|
|
41
52
|
k.add :toggle_new, "Toggle unread/read status of message", 'N'
|
42
53
|
# k.add :collapse_non_new_messages, "Collapse all but unread messages", 'N'
|
43
54
|
k.add :reply, "Reply to a message", 'r'
|
55
|
+
k.add :reply_all, "Reply to all participants of this message", 'G'
|
44
56
|
k.add :forward, "Forward a message or attachment", 'f'
|
57
|
+
k.add :bounce, "Bounce message to other recipient(s)", '!'
|
45
58
|
k.add :alias, "Edit alias/nickname for a person", 'i'
|
46
59
|
k.add :edit_as_new, "Edit message as new", 'D'
|
47
60
|
k.add :save_to_disk, "Save message/attachment to disk", 's'
|
@@ -51,6 +64,9 @@ EOS
|
|
51
64
|
k.add :unsubscribe_from_list, "Subscribe to/unsubscribe from mailing list", ")"
|
52
65
|
k.add :pipe_message, "Pipe message or attachment to a shell command", '|'
|
53
66
|
|
67
|
+
k.add :archive_and_next, "Archive this thread, kill buffer, and view next", 'a'
|
68
|
+
k.add :delete_and_next, "Delete this thread, kill buffer, and view next", 'd'
|
69
|
+
|
54
70
|
k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read:", '.' do |kk|
|
55
71
|
kk.add :archive_and_kill, "Archive this thread and kill buffer", 'a'
|
56
72
|
kk.add :delete_and_kill, "Delete this thread and kill buffer", 'd'
|
@@ -148,16 +164,18 @@ EOS
|
|
148
164
|
update
|
149
165
|
end
|
150
166
|
|
151
|
-
def reply
|
167
|
+
def reply type_arg=nil
|
152
168
|
m = @message_lines[curpos] or return
|
153
|
-
mode = ReplyMode.new m
|
169
|
+
mode = ReplyMode.new m, type_arg
|
154
170
|
BufferManager.spawn "Reply to #{m.subj}", mode
|
155
171
|
end
|
156
172
|
|
173
|
+
def reply_all; reply :all; end
|
174
|
+
|
157
175
|
def subscribe_to_list
|
158
176
|
m = @message_lines[curpos] or return
|
159
|
-
if m.list_subscribe && m.list_subscribe =~ /<mailto:(.*?)\?
|
160
|
-
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => $3
|
177
|
+
if m.list_subscribe && m.list_subscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/
|
178
|
+
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => ($3 || "subscribe")
|
161
179
|
else
|
162
180
|
BufferManager.flash "Can't find List-Subscribe header for this message."
|
163
181
|
end
|
@@ -165,8 +183,8 @@ EOS
|
|
165
183
|
|
166
184
|
def unsubscribe_from_list
|
167
185
|
m = @message_lines[curpos] or return
|
168
|
-
if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)\?
|
169
|
-
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => $3
|
186
|
+
if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/
|
187
|
+
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => ($3 || "unsubscribe")
|
170
188
|
else
|
171
189
|
BufferManager.flash "Can't find List-Unsubscribe header for this message."
|
172
190
|
end
|
@@ -180,6 +198,38 @@ EOS
|
|
180
198
|
end
|
181
199
|
end
|
182
200
|
|
201
|
+
def bounce
|
202
|
+
m = @message_lines[curpos] or return
|
203
|
+
to = BufferManager.ask_for_contacts(:people, "Bounce To: ") or return
|
204
|
+
|
205
|
+
defcmd = AccountManager.default_account.sendmail.sub(/\s(\-(ti|it|t))\b/) do |match|
|
206
|
+
case "$1"
|
207
|
+
when '-t' then ''
|
208
|
+
else ' -i'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
cmd = case (hookcmd = HookManager.run "bounce-command", :from => m.from, :to => to)
|
213
|
+
when nil, /^$/ then defcmd
|
214
|
+
else hookcmd
|
215
|
+
end + ' ' + to.map { |t| t.email }.join(' ')
|
216
|
+
|
217
|
+
bt = to.size > 1 ? "#{to.size} recipients" : to.to_s
|
218
|
+
|
219
|
+
if BufferManager.ask_yes_or_no "Really bounce to #{bt}?"
|
220
|
+
debug "bounce command: #{cmd}"
|
221
|
+
begin
|
222
|
+
IO.popen(cmd, 'w') do |sm|
|
223
|
+
sm.puts m.raw_message
|
224
|
+
end
|
225
|
+
raise SendmailCommandFailed, "Couldn't execute #{cmd}" unless $? == 0
|
226
|
+
rescue SystemCallError, SendmailCommandFailed => e
|
227
|
+
warn "problem sending mail: #{e.message}"
|
228
|
+
BufferManager.flash "Problem sending mail: #{e.message}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
183
233
|
include CanAliasContacts
|
184
234
|
def alias
|
185
235
|
p = @person_lines[curpos] or return
|
@@ -208,7 +258,7 @@ EOS
|
|
208
258
|
new_labels = BufferManager.ask_for_labels :label, "Labels for thread: ", @thread.labels
|
209
259
|
|
210
260
|
return unless new_labels
|
211
|
-
@thread.labels = (reserved_labels + new_labels
|
261
|
+
@thread.labels = Set.new(reserved_labels) + new_labels
|
212
262
|
new_labels.each { |l| LabelManager << l }
|
213
263
|
update
|
214
264
|
UpdateManager.relay self, :labeled, @thread.first
|
@@ -313,16 +363,16 @@ EOS
|
|
313
363
|
end
|
314
364
|
end
|
315
365
|
|
316
|
-
def jump_to_first_open
|
366
|
+
def jump_to_first_open
|
317
367
|
m = @message_lines[0] or return
|
318
368
|
if @layout[m].state != :closed
|
319
|
-
jump_to_message m
|
369
|
+
jump_to_message m#, true
|
320
370
|
else
|
321
|
-
jump_to_next_open
|
371
|
+
jump_to_next_open #true
|
322
372
|
end
|
323
373
|
end
|
324
374
|
|
325
|
-
def jump_to_next_open
|
375
|
+
def jump_to_next_open force_alignment=nil
|
326
376
|
return continue_search_in_buffer if in_search? # hack: allow 'n' to apply to both operations
|
327
377
|
m = (curpos ... @message_lines.length).argfind { |i| @message_lines[i] }
|
328
378
|
return unless m
|
@@ -330,15 +380,15 @@ EOS
|
|
330
380
|
break if @layout[nextm].state != :closed
|
331
381
|
m = nextm
|
332
382
|
end
|
333
|
-
jump_to_message nextm,
|
383
|
+
jump_to_message nextm, force_alignment if nextm
|
334
384
|
end
|
335
385
|
|
336
386
|
def align_current_message
|
337
387
|
m = @message_lines[curpos] or return
|
338
|
-
jump_to_message m
|
388
|
+
jump_to_message m, true
|
339
389
|
end
|
340
390
|
|
341
|
-
def jump_to_prev_open
|
391
|
+
def jump_to_prev_open
|
342
392
|
m = (0 .. curpos).to_a.reverse.argfind { |i| @message_lines[i] } # bah, .to_a
|
343
393
|
return unless m
|
344
394
|
## jump to the top of the current message if we're in the body;
|
@@ -350,38 +400,33 @@ EOS
|
|
350
400
|
break if @layout[prevm].state != :closed
|
351
401
|
m = prevm
|
352
402
|
end
|
353
|
-
jump_to_message prevm
|
403
|
+
jump_to_message prevm if prevm
|
354
404
|
else
|
355
|
-
jump_to_message m
|
405
|
+
jump_to_message m
|
356
406
|
end
|
357
407
|
end
|
358
408
|
|
359
|
-
|
360
|
-
IDEAL_LEFT_CONTEXT = 4 # try and give 4 columns of left context
|
361
|
-
def jump_to_message m, loose_alignment=false
|
409
|
+
def jump_to_message m, force_alignment=false
|
362
410
|
l = @layout[m]
|
363
|
-
left = l.depth * INDENT_SPACES
|
364
|
-
right = left + l.width
|
365
411
|
|
366
|
-
##
|
367
|
-
|
368
|
-
|
369
|
-
else
|
370
|
-
jump_to_line l.top
|
371
|
-
end
|
372
|
-
|
373
|
-
## jump to the left column
|
374
|
-
ideal_left = left +
|
375
|
-
if loose_alignment
|
376
|
-
-IDEAL_LEFT_CONTEXT + (l.width - buffer.content_width + IDEAL_LEFT_CONTEXT + 1).clamp(0, IDEAL_LEFT_CONTEXT)
|
377
|
-
else
|
378
|
-
0
|
379
|
-
end
|
412
|
+
## boundaries of the message
|
413
|
+
message_left = l.depth * INDENT_SPACES
|
414
|
+
message_right = message_left + l.width
|
380
415
|
|
381
|
-
|
416
|
+
## calculate leftmost colum
|
417
|
+
left = if force_alignment # force mode: align exactly
|
418
|
+
message_left
|
419
|
+
else # regular: minimize cursor movement
|
420
|
+
## leftmost and rightmost are boundaries of all valid left-column
|
421
|
+
## alignments.
|
422
|
+
leftmost = [message_left, message_right - buffer.content_width + 1].min
|
423
|
+
rightmost = message_left
|
424
|
+
leftcol.clamp(leftmost, rightmost)
|
425
|
+
end
|
382
426
|
|
383
|
-
|
384
|
-
|
427
|
+
jump_to_line l.top # move vertically
|
428
|
+
jump_to_col left # move horizontally
|
429
|
+
set_cursor_pos l.top # set cursor pos
|
385
430
|
end
|
386
431
|
|
387
432
|
def expand_all_messages
|
data/lib/sup/person.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
module Redwood
|
2
2
|
|
3
|
-
class Person
|
3
|
+
class Person
|
4
4
|
attr_accessor :name, :email
|
5
5
|
|
6
6
|
def initialize name, email
|
7
7
|
raise ArgumentError, "email can't be nil" unless email
|
8
|
-
|
9
|
-
if name
|
10
|
-
|
11
|
-
|
12
|
-
@name = $2
|
13
|
-
end
|
8
|
+
|
9
|
+
@name = if name
|
10
|
+
name = name.strip.gsub(/\s+/, " ")
|
11
|
+
name =~ /^(['"]\s*)(.*?)(\s*["'])$/ ? $2 : name
|
14
12
|
end
|
15
13
|
|
16
|
-
@email = email.
|
14
|
+
@email = email.strip.gsub(/\s+/, " ").downcase
|
17
15
|
end
|
18
16
|
|
19
17
|
def to_s; "#@name <#@email>" end
|
data/lib/sup/poll.rb
CHANGED
@@ -35,22 +35,17 @@ EOS
|
|
35
35
|
@thread = nil
|
36
36
|
@last_poll = nil
|
37
37
|
@polling = false
|
38
|
-
|
39
|
-
self.class.i_am_the_instance self
|
40
|
-
end
|
41
|
-
|
42
|
-
def buffer
|
43
|
-
b, new = BufferManager.spawn_unless_exists("poll for new messages", :hidden => true, :system => true) { PollMode.new }
|
44
|
-
b
|
38
|
+
@mode = nil
|
45
39
|
end
|
46
40
|
|
47
41
|
def poll
|
48
42
|
return if @polling
|
49
43
|
@polling = true
|
44
|
+
@mode ||= PollMode.new
|
50
45
|
HookManager.run "before-poll"
|
51
46
|
|
52
47
|
BufferManager.flash "Polling for new messages..."
|
53
|
-
num, numi, from_and_subj, from_and_subj_inbox =
|
48
|
+
num, numi, from_and_subj, from_and_subj_inbox = @mode.poll
|
54
49
|
if num > 0
|
55
50
|
BufferManager.flash "Loaded #{num.pluralize 'new message'}, #{numi} to inbox."
|
56
51
|
else
|
@@ -83,28 +78,40 @@ EOS
|
|
83
78
|
from_and_subj_inbox = []
|
84
79
|
|
85
80
|
@mutex.synchronize do
|
86
|
-
|
81
|
+
SourceManager.usual_sources.each do |source|
|
87
82
|
# yield "source #{source} is done? #{source.done?} (cur_offset #{source.cur_offset} >= #{source.end_offset})"
|
88
83
|
begin
|
89
84
|
yield "Loading from #{source}... " unless source.done? || (source.respond_to?(:has_errors?) && source.has_errors?)
|
90
85
|
rescue SourceError => e
|
91
|
-
|
86
|
+
warn "problem getting messages from #{source}: #{e.message}"
|
92
87
|
Redwood::report_broken_sources :force_to_top => true
|
93
88
|
next
|
94
89
|
end
|
95
90
|
|
96
91
|
num = 0
|
97
92
|
numi = 0
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
93
|
+
each_message_from source do |m|
|
94
|
+
old_m = Index.build_message m.id
|
95
|
+
if old_m
|
96
|
+
if old_m.source.id != source.id || old_m.source_info != m.source_info
|
97
|
+
## here we merge labels between new and old versions, but we don't let the new
|
98
|
+
## message add :unread or :inbox labels. (they can exist in the old version,
|
99
|
+
## just not be added.)
|
100
|
+
new_labels = old_m.labels + (m.labels - [:unread, :inbox])
|
101
|
+
yield "Message at #{m.source_info} is an updated of an old message. Updating labels from #{m.labels.to_a * ','} => #{new_labels.to_a * ','}"
|
102
|
+
m.labels = new_labels
|
103
|
+
Index.update_message m
|
104
|
+
else
|
105
|
+
yield "Skipping already-imported message at #{m.source_info}"
|
106
|
+
end
|
107
|
+
else
|
108
|
+
yield "Found new message at #{m.source_info} with labels #{m.labels.to_a * ','}"
|
109
|
+
add_new_message m
|
103
110
|
num += 1
|
104
111
|
from_and_subj << [m.from && m.from.longname, m.subj]
|
105
|
-
if m.
|
112
|
+
if (m.labels & [:inbox, :spam, :deleted, :killed]) == Set.new([:inbox])
|
106
113
|
from_and_subj_inbox << [m.from && m.from.longname, m.subj]
|
107
|
-
numi += 1
|
114
|
+
numi += 1
|
108
115
|
end
|
109
116
|
end
|
110
117
|
m
|
@@ -121,51 +128,43 @@ EOS
|
|
121
128
|
[total_num, total_numi, from_and_subj, from_and_subj_inbox]
|
122
129
|
end
|
123
130
|
|
124
|
-
##
|
125
|
-
##
|
131
|
+
## like Source#each, but yields successive Message objects, which have their
|
132
|
+
## labels and offsets set correctly.
|
126
133
|
##
|
127
|
-
##
|
128
|
-
|
129
|
-
## offset, and the index entry on disk (if any). it expects the
|
130
|
-
## yield to return the message (possibly altered in some way), and
|
131
|
-
## then adds it (if new) or updates it (if previously seen).
|
132
|
-
##
|
133
|
-
## the labels of the yielded message are the default source
|
134
|
-
## labels. it is likely that callers will want to replace these with
|
135
|
-
## the index labels, if they exist, so that state is not lost when
|
136
|
-
## e.g. a new version of a message from a mailing list comes in.
|
137
|
-
def add_messages_from source, opts={}
|
134
|
+
## this is the primary mechanism for iterating over messages from a source.
|
135
|
+
def each_message_from source, opts={}
|
138
136
|
begin
|
139
137
|
return if source.done? || source.has_errors?
|
140
138
|
|
141
|
-
source.each do |offset,
|
139
|
+
source.each do |offset, source_labels|
|
142
140
|
if source.has_errors?
|
143
|
-
|
141
|
+
warn "error loading messages from #{source}: #{source.error.message}"
|
144
142
|
return
|
145
143
|
end
|
146
144
|
|
147
|
-
|
148
|
-
labels
|
149
|
-
|
150
|
-
m
|
151
|
-
m.load_from_source!
|
145
|
+
m = Message.build_from_source source, offset
|
146
|
+
m.labels += source_labels + (source.archived? ? [] : [:inbox])
|
147
|
+
m.labels.delete :unread if m.source_marked_read? # preserve read status if possible
|
148
|
+
m.labels.each { |l| LabelManager << l }
|
152
149
|
|
153
|
-
if m.source_marked_read?
|
154
|
-
m.remove_label :unread
|
155
|
-
labels.delete :unread
|
156
|
-
end
|
157
|
-
|
158
|
-
docid, entry = Index.load_entry_for_id m.id
|
159
150
|
HookManager.run "before-add-message", :message => m
|
160
|
-
|
161
|
-
times = Index.sync_message m, false, docid, entry, opts
|
162
|
-
UpdateManager.relay self, :added, m unless entry
|
151
|
+
yield m
|
163
152
|
end
|
164
153
|
rescue SourceError => e
|
165
|
-
|
154
|
+
warn "problem getting messages from #{source}: #{e.message}"
|
166
155
|
Redwood::report_broken_sources :force_to_top => true
|
167
156
|
end
|
168
157
|
end
|
158
|
+
|
159
|
+
## TODO: see if we can do this within PollMode rather than by calling this
|
160
|
+
## method.
|
161
|
+
##
|
162
|
+
## a wrapper around Index.add_message that calls the proper hooks,
|
163
|
+
## does the gui callback stuff, etc.
|
164
|
+
def add_new_message m
|
165
|
+
Index.add_message m
|
166
|
+
UpdateManager.relay self, :added, m
|
167
|
+
end
|
169
168
|
end
|
170
169
|
|
171
170
|
end
|