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.

Files changed (67) hide show
  1. data/CONTRIBUTORS +13 -6
  2. data/History.txt +19 -0
  3. data/ReleaseNotes +35 -0
  4. data/bin/sup +82 -77
  5. data/bin/sup-add +7 -7
  6. data/bin/sup-config +104 -85
  7. data/bin/sup-dump +4 -5
  8. data/bin/sup-recover-sources +9 -10
  9. data/bin/sup-sync +121 -100
  10. data/bin/sup-sync-back +18 -15
  11. data/bin/sup-tweak-labels +24 -21
  12. data/lib/sup.rb +53 -33
  13. data/lib/sup/account.rb +0 -2
  14. data/lib/sup/buffer.rb +47 -22
  15. data/lib/sup/colormap.rb +6 -6
  16. data/lib/sup/contact.rb +0 -2
  17. data/lib/sup/crypto.rb +34 -23
  18. data/lib/sup/draft.rb +6 -14
  19. data/lib/sup/ferret_index.rb +471 -0
  20. data/lib/sup/hook.rb +30 -43
  21. data/lib/sup/hook.rb.BACKUP.8625.rb +158 -0
  22. data/lib/sup/hook.rb.BACKUP.8681.rb +158 -0
  23. data/lib/sup/hook.rb.BASE.8625.rb +155 -0
  24. data/lib/sup/hook.rb.BASE.8681.rb +155 -0
  25. data/lib/sup/hook.rb.LOCAL.8625.rb +142 -0
  26. data/lib/sup/hook.rb.LOCAL.8681.rb +142 -0
  27. data/lib/sup/hook.rb.REMOTE.8625.rb +145 -0
  28. data/lib/sup/hook.rb.REMOTE.8681.rb +145 -0
  29. data/lib/sup/imap.rb +18 -8
  30. data/lib/sup/index.rb +70 -528
  31. data/lib/sup/interactive-lock.rb +74 -0
  32. data/lib/sup/keymap.rb +26 -26
  33. data/lib/sup/label.rb +2 -4
  34. data/lib/sup/logger.rb +54 -35
  35. data/lib/sup/maildir.rb +41 -6
  36. data/lib/sup/mbox.rb +1 -1
  37. data/lib/sup/mbox/loader.rb +18 -6
  38. data/lib/sup/mbox/ssh-file.rb +1 -7
  39. data/lib/sup/message-chunks.rb +36 -23
  40. data/lib/sup/message.rb +126 -46
  41. data/lib/sup/mode.rb +3 -2
  42. data/lib/sup/modes/console-mode.rb +108 -0
  43. data/lib/sup/modes/edit-message-mode.rb +15 -5
  44. data/lib/sup/modes/inbox-mode.rb +2 -4
  45. data/lib/sup/modes/label-list-mode.rb +1 -1
  46. data/lib/sup/modes/line-cursor-mode.rb +18 -18
  47. data/lib/sup/modes/log-mode.rb +29 -16
  48. data/lib/sup/modes/poll-mode.rb +7 -9
  49. data/lib/sup/modes/reply-mode.rb +5 -3
  50. data/lib/sup/modes/scroll-mode.rb +2 -2
  51. data/lib/sup/modes/search-results-mode.rb +9 -11
  52. data/lib/sup/modes/text-mode.rb +2 -2
  53. data/lib/sup/modes/thread-index-mode.rb +26 -16
  54. data/lib/sup/modes/thread-view-mode.rb +84 -39
  55. data/lib/sup/person.rb +6 -8
  56. data/lib/sup/poll.rb +46 -47
  57. data/lib/sup/rfc2047.rb +1 -5
  58. data/lib/sup/sent.rb +27 -20
  59. data/lib/sup/source.rb +90 -13
  60. data/lib/sup/textfield.rb +4 -4
  61. data/lib/sup/thread.rb +15 -13
  62. data/lib/sup/undo.rb +0 -1
  63. data/lib/sup/update.rb +0 -1
  64. data/lib/sup/util.rb +51 -43
  65. data/lib/sup/xapian_index.rb +566 -0
  66. metadata +57 -46
  67. data/lib/sup/suicide.rb +0 -36
@@ -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 => 1, :background => false
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 true
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.save Index
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
- thread.labels = keepl + user_labels
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 = t.map do |m, *o|
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.compact.uniq.map { |a| [a, new[a]] }
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, "+#{label} "] } +
848
- [[:snippet_color, snippet]
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:(.*?)\?(subject=(.*?))>/
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:(.*?)\?(subject=(.*?))>/
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).uniq
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 loose_alignment=false
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, loose_alignment
369
+ jump_to_message m#, true
320
370
  else
321
- jump_to_next_open loose_alignment
371
+ jump_to_next_open #true
322
372
  end
323
373
  end
324
374
 
325
- def jump_to_next_open loose_alignment=false
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, loose_alignment if 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 loose_alignment=false
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, loose_alignment if prevm
403
+ jump_to_message prevm if prevm
354
404
  else
355
- jump_to_message m, loose_alignment
405
+ jump_to_message m
356
406
  end
357
407
  end
358
408
 
359
- IDEAL_TOP_CONTEXT = 3 # try and give 3 rows of top context
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
- ## jump to the top line
367
- if loose_alignment
368
- jump_to_line [l.top - IDEAL_TOP_CONTEXT, 0].max # give 3 lines of top context
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
- jump_to_col [ideal_left, 0].max
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
- ## either way, move the cursor to the first line
384
- set_cursor_pos l.top
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
@@ -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
- @name = name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
11
- if @name =~ /^(['"]\s*)(.*?)(\s*["'])$/
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.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ").downcase
14
+ @email = email.strip.gsub(/\s+/, " ").downcase
17
15
  end
18
16
 
19
17
  def to_s; "#@name <#@email>" end
@@ -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 = buffer.mode.poll
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
- Index.usual_sources.each do |source|
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
- Redwood::log "problem getting messages from #{source}: #{e.message}"
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
- add_messages_from source do |m, offset, entry|
99
- ## always preserve the labels on disk.
100
- m.labels = ((m.labels - [:unread, :inbox]) + entry[:label].symbolistize).uniq if entry
101
- yield "Found message at #{offset} with labels {#{m.labels * ', '}}"
102
- unless entry
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.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
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
- ## this is the main mechanism for adding new messages to the
125
- ## index. it's called both by sup-sync and by PollMode.
131
+ ## like Source#each, but yields successive Message objects, which have their
132
+ ## labels and offsets set correctly.
126
133
  ##
127
- ## for each message in the source, starting from the source's
128
- ## starting offset, this methods yields the message, the source
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, labels|
139
+ source.each do |offset, source_labels|
142
140
  if source.has_errors?
143
- Redwood::log "error loading messages from #{source}: #{source.error.message}"
141
+ warn "error loading messages from #{source}: #{source.error.message}"
144
142
  return
145
143
  end
146
144
 
147
- labels.each { |l| LabelManager << l }
148
- labels = labels + (source.archived? ? [] : [:inbox])
149
-
150
- m = Message.new :source => source, :source_info => offset, :labels => labels
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
- m = yield(m, offset, entry) or next if block_given?
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
- Redwood::log "problem getting messages from #{source}: #{e.message}"
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