sup 0.0.2 → 0.0.3

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.

@@ -7,7 +7,7 @@ class InboxMode < ThreadIndexMode
7
7
  ## overwrite toggle_archived with archive
8
8
  k.add :archive, "Archive thread (remove from inbox)", 'a'
9
9
  k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
10
- k.add :reload, "Discard threads and reload", 'R'
10
+ k.add :reload, "Discard threads and reload", 'D'
11
11
  end
12
12
 
13
13
  def initialize
@@ -3,7 +3,7 @@ module Redwood
3
3
  class LabelListMode < LineCursorMode
4
4
  register_keymap do |k|
5
5
  k.add :view_results, "View messages with the selected label", :enter
6
- k.add :reload, "Reload", "R"
6
+ k.add :reload, "Discard results and reload", 'D'
7
7
  end
8
8
 
9
9
  def initialize
@@ -79,7 +79,6 @@ protected
79
79
  if @curpos == topline
80
80
  page_up
81
81
  set_cursor_pos [botline - 2, topline].max
82
- # raise "cursor position now #@curpos, topline #{topline} botline #{botline}"
83
82
  else
84
83
  @curpos -= 1
85
84
  unless buffer.dirty?
@@ -77,6 +77,11 @@ class ScrollMode < Mode
77
77
  @botline = [@topline + buffer.content_height, lines].min
78
78
  end
79
79
 
80
+ def resize *a
81
+ super *a
82
+ ensure_mode_validity
83
+ end
84
+
80
85
  protected
81
86
 
82
87
  def draw_line ln, opts={}
@@ -86,13 +91,18 @@ protected
86
91
  :highlight => opts[:highlight]
87
92
  when Array
88
93
  xpos = 0
94
+
95
+ ## speed test
96
+ # str = s.map { |color, text| text }.join
97
+ # buffer.write ln - @topline, 0, str, :color => :none, :highlight => opts[:highlight]
98
+ # return
99
+
89
100
  s.each do |color, text|
90
- raise "nil text for color '#{color}'" if text.nil?
101
+ raise "nil text for color '#{color}'" if text.nil? # good for debugging
91
102
  if xpos + text.length < @leftcol
92
103
  buffer.write ln - @topline, 0, "", :color => color,
93
104
  :highlight => opts[:highlight]
94
105
  xpos += text.length
95
- ## nothing
96
106
  elsif xpos < @leftcol
97
107
  ## partial
98
108
  buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
@@ -5,9 +5,8 @@ class SearchResultsMode < ThreadIndexMode
5
5
  k.add :load_more_threads, "Load #{LOAD_MORE_THREAD_NUM} more threads", 'M'
6
6
  end
7
7
 
8
- def initialize content
9
- raise ArgumentError, "no content" if content =~ /^\s*$/
10
- @content = content.gsub(/[\(\)]/) { |x| "\\" + x }
8
+ def initialize qobj
9
+ @qobj = qobj
11
10
  super
12
11
  end
13
12
 
@@ -15,7 +14,7 @@ class SearchResultsMode < ThreadIndexMode
15
14
  def is_relevant? m; super; end
16
15
 
17
16
  def load_more_threads n=ThreadIndexMode::LOAD_MORE_THREAD_NUM
18
- load_n_threads_background n, :content => @content,
17
+ load_n_threads_background n, :qobj => @qobj,
19
18
  :load_killed => true,
20
19
  :load_spam => false,
21
20
  :when_done =>(lambda do |num|
@@ -4,7 +4,7 @@ class TextMode < ScrollMode
4
4
  attr_reader :text
5
5
 
6
6
  def initialize text=""
7
- @text = text
7
+ @text = text.normalize_whitespace
8
8
  update_lines
9
9
  buffer.mark_dirty if buffer
10
10
  super()
@@ -47,8 +47,11 @@ class ThreadIndexMode < LineCursorMode
47
47
  t = @threads[this_curpos]
48
48
 
49
49
  ## TODO: don't regen text completely
50
- mode = ThreadViewMode.new t, @hidden_labels
51
- BufferManager.spawn t.subj, mode
50
+ Redwood::reporting_thread do
51
+ mode = ThreadViewMode.new t, @hidden_labels
52
+ BufferManager.spawn t.subj, mode
53
+ BufferManager.draw_screen
54
+ end
52
55
  end
53
56
 
54
57
  def handle_starred_update m
@@ -287,9 +290,7 @@ class ThreadIndexMode < LineCursorMode
287
290
  update
288
291
  BufferManager.clear @mbid
289
292
  @mbid = nil
290
-
291
293
  BufferManager.draw_screen
292
-
293
294
  @ts.size - orig_size
294
295
  end
295
296
 
@@ -320,6 +321,7 @@ protected
320
321
  end
321
322
 
322
323
  def update_text_for_line l
324
+ return unless l # not sure why this happens, but it does, occasionally
323
325
  @text[l] = text_for_thread @threads[l]
324
326
  buffer.mark_dirty if buffer
325
327
  end
@@ -359,11 +361,11 @@ protected
359
361
  [base_color, sprintf("%-#{@from_width}s", from)],
360
362
  [:starred_color, starred ? "*" : " "],
361
363
  [:none, t.size == 1 ? " " * (@size_width + 2) : sprintf("(%#{@size_width}d)", t.size)],
362
- [:to_me_color, dp ? " >" : (p ? ' -' : " ")],
363
- [base_color, t.subj]
364
+ [:to_me_color, dp ? " >" : (p ? ' +' : " ")],
365
+ [base_color, t.subj + (t.subj.empty? ? "" : " ")],
364
366
  ] +
365
- (t.labels - @hidden_labels).map { |label| [:label_color, " +#{label}"] } +
366
- [[:snippet_color, " " + t.snippet]
367
+ (t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } +
368
+ [[:snippet_color, t.snippet]
367
369
  ]
368
370
  end
369
371
 
@@ -46,8 +46,10 @@ class ThreadViewMode < LineCursorMode
46
46
  end
47
47
  @state[latest] = :open if @state[latest] == :closed
48
48
 
49
- regen_chunks
50
- regen_text
49
+ BufferManager.say "Loading message bodies..." do
50
+ regen_chunks
51
+ regen_text
52
+ end
51
53
  end
52
54
 
53
55
  def draw_line ln, opts={}
@@ -141,6 +143,7 @@ class ThreadViewMode < LineCursorMode
141
143
  if m.is_draft?
142
144
  mode = ResumeMode.new m
143
145
  BufferManager.spawn "Edit message", mode
146
+ mode.edit
144
147
  else
145
148
  BufferManager.flash "Not a draft message!"
146
149
  end
@@ -210,8 +213,6 @@ class ThreadViewMode < LineCursorMode
210
213
  UpdateManager.relay :read, m
211
214
  end
212
215
  end
213
-
214
- Redwood::log "releasing chunks and text from \"#{buffer.title}\""
215
216
  @messages = @chunks = @text = nil
216
217
  end
217
218
 
@@ -281,7 +282,7 @@ private
281
282
  when :open
282
283
  [[prefix_widget, widget, imp_widget,
283
284
  [:message_patina_color,
284
- "#{m.from ? m.from.mediumname : '?'} to #{m.to.map { |l| l.shortname }.join(', ')} #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})"]]]
285
+ "#{m.from ? m.from.mediumname : '?'} to #{m.recipients.map { |l| l.shortname }.join(', ')} #{m.date.to_nice_s} (#{m.date.to_nice_distance_s})"]]]
285
286
  # (m.to.empty? ? [] : [[[:message_patina_color, prefix + " To: " + m.recipients.map { |x| x.mediumname }.join(", ")]]]) +
286
287
  when :closed
287
288
  [[prefix_widget, widget, imp_widget,
@@ -291,13 +292,13 @@ private
291
292
  labels = m.labels# - @hidden_labels
292
293
  x = [[prefix_widget, widget, imp_widget, [:message_patina_color, "From: #{m.from ? m.from.longname : '?'}"]]] +
293
294
  ((m.to.empty? ? [] : break_into_lines(" To: ", m.to.map { |x| x.longname })) +
294
- (m.cc.empty? ? [] : break_into_lines(" Cc: ", m.cc.map { |x| x.longname })) +
295
- (m.bcc.empty? ? [] : break_into_lines(" Bcc: ", m.bcc.map { |x| x.longname })) +
296
- [" Date: #{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})"] +
297
- [" Subject: #{m.subj}"] +
298
- [(parent ? " In reply to: #{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}" : nil)] +
299
- [labels.empty? ? nil : " Labels: #{labels.join(', ')}"]
300
- ).flatten.compact.map { |l| [[:message_patina_color, prefix + " " + l]] }
295
+ (m.cc.empty? ? [] : break_into_lines(" Cc: ", m.cc.map { |x| x.longname })) +
296
+ (m.bcc.empty? ? [] : break_into_lines(" Bcc: ", m.bcc.map { |x| x.longname })) +
297
+ [" Date: #{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})"] +
298
+ [" Subject: #{m.subj}"] +
299
+ [(parent ? " In reply to: #{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}" : nil)] +
300
+ [labels.empty? ? nil : " Labels: #{labels.join(', ')}"]
301
+ ).flatten.compact.map { |l| [[:message_patina_color, prefix + " " + l]] }
301
302
  #raise x.inspect
302
303
  x
303
304
  end
@@ -1,5 +1,31 @@
1
1
  module Redwood
2
2
 
3
+ class PersonManager
4
+ include Singleton
5
+
6
+ def initialize fn
7
+ @fn = fn
8
+ @names = {}
9
+ IO.readlines(fn).map { |l| l =~ /^(.*)?:\s+(\d+)\s+(.*)$/ && @names[$1] = [$2.to_i, $3] } if File.exists? fn
10
+ self.class.i_am_the_instance self
11
+ end
12
+
13
+ def name_for email; @names.member?(email) && @names[email][1]; end
14
+ def register email, name
15
+ return unless name
16
+
17
+ name = name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
18
+
19
+ ## all else being equal, prefer longer names, unless the prior name
20
+ ## doesn't contain any capitalization
21
+ oldcount, oldname = @names[email]
22
+ @names[email] = [0, name] if oldname.nil? || oldname.length < name.length || (oldname !~ /[A-Z]/ && name =~ /[A-Z]/)
23
+ @names[email][0] = Time.now.to_i
24
+ end
25
+
26
+ def save; File.open(@fn, "w") { |f| @names.each { |email, (time, name)| f.puts "#{email}: #{time} #{name}" } }; end
27
+ end
28
+
3
29
  class Person
4
30
  @@email_map = {}
5
31
 
@@ -7,22 +33,14 @@ class Person
7
33
 
8
34
  def initialize name, email
9
35
  raise ArgumentError, "email can't be nil" unless email
10
- @name =
11
- if name
12
- name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
13
- else
14
- nil
15
- end
16
36
  @email = email.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ").downcase
17
- @@email_map[@email] = self
37
+ PersonManager.register @email, name
38
+ @name = PersonManager.name_for @email
18
39
  end
19
40
 
20
41
  def == o; o && o.email == email; end
21
42
  alias :eql? :==
22
-
23
- def hash
24
- [name, email].hash
25
- end
43
+ def hash; [name, email].hash; end
26
44
 
27
45
  def shortname
28
46
  case @name
@@ -31,9 +49,9 @@ class Person
31
49
  when /(\S+) \S+/
32
50
  $1
33
51
  when nil
34
- @email #[0 ... 10]
52
+ @email
35
53
  else
36
- @name #[0 ... 10]
54
+ @name
37
55
  end
38
56
  end
39
57
 
@@ -45,18 +63,12 @@ class Person
45
63
  end
46
64
  end
47
65
 
48
- def mediumname
49
- if @name
50
- name
51
- else
52
- @email
53
- end
54
- end
66
+ def mediumname; @name || @email; end
55
67
 
56
68
  def full_address
57
69
  if @name && @email
58
70
  if @name =~ /"/
59
- "#{@name.inspect} <#@email>"
71
+ "#{@name.inspect} <#@email>" # escape quotes
60
72
  else
61
73
  "#@name <#@email>"
62
74
  end
@@ -65,6 +77,7 @@ class Person
65
77
  end
66
78
  end
67
79
 
80
+ ## when sorting addresses, sort by this
68
81
  def sort_by_me
69
82
  case @name
70
83
  when /^(\S+), \S+/
@@ -80,18 +93,10 @@ class Person
80
93
  end.downcase
81
94
  end
82
95
 
83
- def self.for_several s
84
- return [] if s.nil?
85
-
86
- begin
87
- s.split_on_commas.map { |ss| self.for ss }
88
- rescue StandardError => e
89
- raise "#{e.message}: for #{s.inspect}"
90
- end
91
- end
92
-
93
96
  def self.for s
94
97
  return nil if s.nil?
98
+
99
+ ## try and parse an email address and name
95
100
  name, email =
96
101
  case s
97
102
  when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
@@ -105,15 +110,13 @@ class Person
105
110
  [nil, s]
106
111
  end
107
112
 
108
- if name && (p = @@email_map[email])
109
- ## all else being equal, prefer longer names, unless the prior name
110
- ## doesn't contain any capitalization
111
- p.name = name if (p.name.nil? || p.name.length < name.length) unless
112
- p.name =~ /[A-Z]/ || (AccountManager.instantiated? && AccountManager.is_account?(p))
113
- p
114
- else
115
- Person.new name, email
116
- end
113
+ @@email_map[email] ||= Person.new name, email
114
+ end
115
+
116
+ def self.for_several s
117
+ return [] if s.nil?
118
+
119
+ s.split_on_commas.map { |ss| self.for ss }
117
120
  end
118
121
  end
119
122
 
@@ -12,13 +12,6 @@ class PollManager
12
12
  @last_poll = nil
13
13
 
14
14
  self.class.i_am_the_instance self
15
-
16
- Redwood::reporting_thread do
17
- while true
18
- sleep DELAY / 2
19
- poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
20
- end
21
- end
22
15
  end
23
16
 
24
17
  def buffer
@@ -38,6 +31,15 @@ class PollManager
38
31
  [num, numi]
39
32
  end
40
33
 
34
+ def start_thread
35
+ Redwood::reporting_thread do
36
+ while true
37
+ sleep DELAY / 2
38
+ poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
39
+ end
40
+ end
41
+ end
42
+
41
43
  def do_poll
42
44
  return [0, 0] if @polling
43
45
  @polling = true
@@ -54,7 +56,7 @@ class PollManager
54
56
  num_inbox = 0
55
57
  source.each do |offset, labels|
56
58
  start_offset ||= offset
57
- yield " Found message at #{offset} with labels #{labels * ', '}"
59
+ yield "Found message at #{offset} with labels #{labels * ', '}"
58
60
  begin
59
61
  m = Redwood::Message.new :source => source, :source_info => offset,
60
62
  :labels => labels
@@ -71,7 +73,7 @@ class PollManager
71
73
  total_num += 1
72
74
  total_numi += 1 if m.labels.include? :inbox
73
75
  end
74
- rescue Redwood::MessageFormatError => e
76
+ rescue SourceError, MessageFormatError => e
75
77
  yield "Ignoring erroneous message at #{source}##{offset}: #{e.message}"
76
78
  end
77
79
 
@@ -6,13 +6,10 @@ class Source
6
6
  ## dirty? described whether cur_offset has changed, which means the
7
7
  ## source needs to be re-saved to disk.
8
8
  ##
9
- ## broken? means no message can be loaded (e.g. IMAP server is
10
- ## down), so don't even bother.
11
- bool_reader :usual, :archived, :dirty
12
- attr_reader :cur_offset, :broken_msg
13
- attr_accessor :id
9
+ ## broken? means no message can be loaded, e.g. IMAP server is
10
+ ## down, mbox file is corrupt and needs to be rescanned.
14
11
 
15
- ## You should implement:
12
+ ## When writing a new source, you should implement:
16
13
  ##
17
14
  ## start_offset
18
15
  ## end_offset
@@ -20,11 +17,19 @@ class Source
20
17
  ## load_message(offset)
21
18
  ## raw_header(offset)
22
19
  ## raw_full_message(offset)
23
- ## next
20
+ ## next (or each, if you prefer)
21
+
22
+ ## you can throw SourceErrors from any of those, but we don't catch
23
+ ## anything else, so make sure you catch all non-fatal errors and
24
+ ## reraise them as source errors.
25
+
26
+ bool_reader :usual, :archived, :dirty
27
+ attr_reader :uri, :cur_offset, :broken_msg
28
+ attr_accessor :id
24
29
 
25
30
  def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil
26
31
  @uri = uri
27
- @cur_offset = initial_offset || start_offset
32
+ @cur_offset = initial_offset
28
33
  @usual = usual
29
34
  @archived = archived
30
35
  @id = id
@@ -35,28 +40,49 @@ class Source
35
40
  def broken?; !@broken_msg.nil?; end
36
41
  def to_s; @uri; end
37
42
  def seek_to! o; self.cur_offset = o; end
38
- def reset!; seek_to! start_offset; end
43
+ def reset!
44
+ return if broken?
45
+ begin
46
+ seek_to! start_offset
47
+ rescue SourceError
48
+ end
49
+ end
39
50
  def == o; o.to_s == to_s; end
40
- def done?; cur_offset >= end_offset; end
51
+ def done?;
52
+ return true if broken?
53
+ begin
54
+ (self.cur_offset ||= start_offset) >= end_offset
55
+ rescue SourceError => e
56
+ true
57
+ end
58
+ end
41
59
  def is_source_for? s; to_s == s; end
42
60
 
43
61
  def each
44
- until done?
45
- n, labels = self.next
46
- raise "no message" unless n
47
- labels += [:inbox] unless archived?
48
- yield n, labels
62
+ return if broken?
63
+ begin
64
+ self.cur_offset ||= start_offset
65
+ until done? || broken? # just like life!
66
+ n, labels = self.next
67
+ raise "no message" unless n
68
+ yield n, labels
69
+ end
70
+ rescue SourceError => e
71
+ self.broken_msg = e.message
49
72
  end
50
73
  end
51
74
 
52
75
  protected
53
-
76
+
54
77
  def cur_offset= o
55
78
  @cur_offset = o
56
79
  @dirty = true
57
80
  end
58
-
59
- attr_writer :broken_msg
81
+
82
+ def broken_msg= m
83
+ @broken_msg = m
84
+ # Redwood::log "#{to_s}: #{m}"
85
+ end
60
86
  end
61
87
 
62
88
  Redwood::register_yaml(Source, %w(uri cur_offset usual archived id))