sup 0.7 → 0.8
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 +8 -3
- data/History.txt +19 -0
- data/README.txt +45 -44
- data/ReleaseNotes +6 -0
- data/bin/sup +36 -5
- data/bin/sup-add +0 -0
- data/bin/sup-config +0 -0
- data/bin/sup-dump +0 -0
- data/bin/sup-recover-sources +8 -12
- data/bin/sup-sync +22 -16
- data/bin/sup-sync-back +1 -1
- data/bin/sup-tweak-labels +8 -8
- data/lib/sup.rb +3 -17
- data/lib/sup/account.rb +2 -3
- data/lib/sup/buffer.rb +21 -10
- data/lib/sup/colormap.rb +30 -27
- data/lib/sup/contact.rb +1 -1
- data/lib/sup/draft.rb +1 -3
- data/lib/sup/imap.rb +1 -1
- data/lib/sup/index.rb +70 -48
- data/lib/sup/label.rb +12 -10
- data/lib/sup/logger.rb +1 -1
- data/lib/sup/maildir.rb +1 -1
- data/lib/sup/mbox.rb +13 -70
- data/lib/sup/mbox/loader.rb +26 -15
- data/lib/sup/message-chunks.rb +18 -6
- data/lib/sup/message.rb +56 -67
- data/lib/sup/mode.rb +2 -1
- data/lib/sup/modes/buffer-list-mode.rb +6 -2
- data/lib/sup/modes/compose-mode.rb +0 -1
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +37 -9
- data/lib/sup/modes/inbox-mode.rb +34 -0
- data/lib/sup/modes/label-list-mode.rb +10 -3
- data/lib/sup/modes/reply-mode.rb +24 -13
- data/lib/sup/modes/resume-mode.rb +2 -0
- data/lib/sup/modes/scroll-mode.rb +10 -9
- data/lib/sup/modes/search-results-mode.rb +2 -2
- data/lib/sup/modes/thread-index-mode.rb +157 -38
- data/lib/sup/modes/thread-view-mode.rb +27 -11
- data/lib/sup/person.rb +22 -73
- data/lib/sup/poll.rb +18 -20
- data/lib/sup/source.rb +44 -0
- data/lib/sup/undo.rb +39 -0
- data/lib/sup/util.rb +25 -16
- metadata +46 -45
data/lib/sup/label.rb
CHANGED
@@ -7,9 +7,6 @@ class LabelManager
|
|
7
7
|
## add/remove these via normal label mechanisms.
|
8
8
|
RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox, :attachment ]
|
9
9
|
|
10
|
-
## labels which it nonetheless makes sense to search for by
|
11
|
-
LISTABLE_RESERVED_LABELS = [ :starred, :spam, :draft, :sent, :killed, :deleted, :inbox, :attachment ]
|
12
|
-
|
13
10
|
## labels that will typically be hidden from the user
|
14
11
|
HIDDEN_RESERVED_LABELS = [ :starred, :unread, :attachment ]
|
15
12
|
|
@@ -22,31 +19,34 @@ class LabelManager
|
|
22
19
|
[]
|
23
20
|
end
|
24
21
|
@labels = {}
|
22
|
+
@new_labels = {}
|
25
23
|
@modified = false
|
26
24
|
labels.each { |t| @labels[t] = true }
|
27
25
|
|
28
26
|
self.class.i_am_the_instance self
|
29
27
|
end
|
30
28
|
|
31
|
-
|
29
|
+
def new_label? l; @new_labels.include?(l) end
|
30
|
+
|
31
|
+
## all labels user-defined and system, ordered
|
32
32
|
## nicely and converted to pretty strings. use #label_for to recover
|
33
33
|
## the original label.
|
34
|
-
def
|
34
|
+
def all_labels
|
35
35
|
## uniq's only necessary here because of certain upgrade issues
|
36
|
-
(
|
36
|
+
(RESERVED_LABELS + @labels.keys).uniq
|
37
37
|
end
|
38
38
|
|
39
|
-
## all
|
39
|
+
## all user-defined labels, ordered
|
40
40
|
## nicely and converted to pretty strings. use #label_for to recover
|
41
41
|
## the original label.
|
42
|
-
def
|
42
|
+
def user_defined_labels
|
43
43
|
@labels.keys
|
44
44
|
end
|
45
45
|
|
46
46
|
## reverse the label->string mapping, for convenience!
|
47
47
|
def string_for l
|
48
48
|
if RESERVED_LABELS.include? l
|
49
|
-
l.to_s.
|
49
|
+
l.to_s.capitalize
|
50
50
|
else
|
51
51
|
l.to_s
|
52
52
|
end
|
@@ -66,12 +66,13 @@ class LabelManager
|
|
66
66
|
t = t.intern unless t.is_a? Symbol
|
67
67
|
unless @labels.member?(t) || RESERVED_LABELS.member?(t)
|
68
68
|
@labels[t] = true
|
69
|
+
@new_labels[t] = true
|
69
70
|
@modified = true
|
70
71
|
end
|
71
72
|
end
|
72
73
|
|
73
74
|
def delete t
|
74
|
-
if @labels.delete
|
75
|
+
if @labels.delete(t)
|
75
76
|
@modified = true
|
76
77
|
end
|
77
78
|
end
|
@@ -79,6 +80,7 @@ class LabelManager
|
|
79
80
|
def save
|
80
81
|
return unless @modified
|
81
82
|
File.open(@fn, "w") { |f| f.puts @labels.keys.sort_by { |l| l.to_s } }
|
83
|
+
@new_labels = {}
|
82
84
|
end
|
83
85
|
end
|
84
86
|
|
data/lib/sup/logger.rb
CHANGED
@@ -18,7 +18,7 @@ class Logger
|
|
18
18
|
def make_buf
|
19
19
|
return if @mode.buffer || !BufferManager.instantiated? || !@respawn || @spawning
|
20
20
|
@spawning = true
|
21
|
-
@mode.buffer = BufferManager.instance.spawn "
|
21
|
+
@mode.buffer = BufferManager.instance.spawn "log", @mode, :hidden => true, :system => true
|
22
22
|
@spawning = false
|
23
23
|
end
|
24
24
|
|
data/lib/sup/maildir.rb
CHANGED
data/lib/sup/mbox.rb
CHANGED
@@ -1,81 +1,24 @@
|
|
1
1
|
require "sup/mbox/loader"
|
2
2
|
require "sup/mbox/ssh-file"
|
3
3
|
require "sup/mbox/ssh-loader"
|
4
|
-
require "sup/rfc2047"
|
5
4
|
|
6
5
|
module Redwood
|
7
6
|
|
8
|
-
## some utility functions. actually these are not mbox-specific at all
|
9
|
-
## and should be moved somewhere else.
|
10
|
-
##
|
11
|
-
## TODO: move functionality to somewhere better, like message.rb
|
12
7
|
module MBox
|
13
|
-
BREAK_RE = /^From \S
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
when /^(Delivered-To):#{HEADER_RE}$/i,
|
26
|
-
/^(X-Original-To):#{HEADER_RE}$/i,
|
27
|
-
/^(Envelope-To):#{HEADER_RE}$/i: header[last = $1] ||= $2
|
28
|
-
|
29
|
-
when /^(From):#{HEADER_RE}$/i,
|
30
|
-
/^(To):#{HEADER_RE}$/i,
|
31
|
-
/^(Cc):#{HEADER_RE}$/i,
|
32
|
-
/^(Bcc):#{HEADER_RE}$/i,
|
33
|
-
/^(Subject):#{HEADER_RE}$/i,
|
34
|
-
/^(Date):#{HEADER_RE}$/i,
|
35
|
-
/^(References):#{HEADER_RE}$/i,
|
36
|
-
/^(In-Reply-To):#{HEADER_RE}$/i,
|
37
|
-
/^(Reply-To):#{HEADER_RE}$/i,
|
38
|
-
/^(List-Post):#{HEADER_RE}$/i,
|
39
|
-
/^(List-Subscribe):#{HEADER_RE}$/i,
|
40
|
-
/^(List-Unsubscribe):#{HEADER_RE}$/i,
|
41
|
-
/^(Status):#{HEADER_RE}$/i,
|
42
|
-
/^(X-\S+):#{HEADER_RE}$/: header[last = $1] = $2
|
43
|
-
when /^(Message-Id):#{HEADER_RE}$/i: header[mid_field = last = $1] = $2
|
44
|
-
|
45
|
-
when /^\r*$/: break
|
46
|
-
when /^\S+:/: last = nil # some other header we don't care about
|
47
|
-
else
|
48
|
-
header[last] += " " + line.chomp.gsub(/^\s+/, "") if last
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
if mid_field && header[mid_field] && header[mid_field] =~ /<(.*?)>/
|
53
|
-
header[mid_field] = $1
|
8
|
+
BREAK_RE = /^From \S+ (.+)$/
|
9
|
+
|
10
|
+
def is_break_line? l
|
11
|
+
l =~ BREAK_RE or return false
|
12
|
+
time = $1
|
13
|
+
begin
|
14
|
+
## hack -- make Time.parse fail when trying to substitute values from Time.now
|
15
|
+
Time.parse time, 0
|
16
|
+
true
|
17
|
+
rescue NoMethodError
|
18
|
+
Redwood::log "found invalid date in potential mbox split line, not splitting: #{l.inspect}"
|
19
|
+
false
|
54
20
|
end
|
55
|
-
|
56
|
-
header.each do |k, v|
|
57
|
-
next unless Rfc2047.is_encoded? v
|
58
|
-
header[k] =
|
59
|
-
begin
|
60
|
-
Rfc2047.decode_to $encoding, v
|
61
|
-
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
|
62
|
-
Redwood::log "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
|
63
|
-
v
|
64
|
-
end
|
65
|
-
end
|
66
|
-
header
|
67
21
|
end
|
68
|
-
|
69
|
-
## never actually called
|
70
|
-
def read_body f
|
71
|
-
body = []
|
72
|
-
f.each_line do |l|
|
73
|
-
break if l =~ BREAK_RE
|
74
|
-
body << l.chomp
|
75
|
-
end
|
76
|
-
body
|
77
|
-
end
|
78
|
-
|
79
|
-
module_function :read_header, :read_body
|
22
|
+
module_function :is_break_line?
|
80
23
|
end
|
81
24
|
end
|
data/lib/sup/mbox/loader.rb
CHANGED
@@ -9,7 +9,7 @@ class Loader < Source
|
|
9
9
|
attr_accessor :labels
|
10
10
|
|
11
11
|
## uri_or_fp is horrific. need to refactor.
|
12
|
-
def initialize uri_or_fp, start_offset=
|
12
|
+
def initialize uri_or_fp, start_offset=0, usual=true, archived=false, id=nil, labels=[]
|
13
13
|
@mutex = Mutex.new
|
14
14
|
@labels = ((labels || []) - LabelManager::RESERVED_LABELS).uniq.freeze
|
15
15
|
|
@@ -56,10 +56,10 @@ class Loader < Source
|
|
56
56
|
@mutex.synchronize do
|
57
57
|
@f.seek offset
|
58
58
|
l = @f.gets
|
59
|
-
unless l
|
59
|
+
unless MBox::is_break_line? l
|
60
60
|
raise OutOfSyncSourceError, "mismatch in mbox file offset #{offset.inspect}: #{l.inspect}."
|
61
61
|
end
|
62
|
-
header =
|
62
|
+
header = parse_raw_email_header @f
|
63
63
|
end
|
64
64
|
header
|
65
65
|
end
|
@@ -68,25 +68,36 @@ class Loader < Source
|
|
68
68
|
@mutex.synchronize do
|
69
69
|
@f.seek offset
|
70
70
|
begin
|
71
|
-
RMail::Mailbox::MBoxReader
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
71
|
+
## don't use RMail::Mailbox::MBoxReader because it doesn't properly ignore
|
72
|
+
## "From" at the start of a message body line.
|
73
|
+
string = ""
|
74
|
+
l = @f.gets
|
75
|
+
string << l until @f.eof? || MBox::is_break_line?(l = @f.gets)
|
76
|
+
RMail::Parser.read string
|
78
77
|
rescue RMail::Parser::Error => e
|
79
78
|
raise FatalSourceError, "error parsing mbox file: #{e.message}"
|
80
79
|
end
|
81
80
|
end
|
82
81
|
end
|
83
82
|
|
83
|
+
## scan forward until we're at the valid start of a message
|
84
|
+
def correct_offset!
|
85
|
+
@mutex.synchronize do
|
86
|
+
@f.seek cur_offset
|
87
|
+
string = ""
|
88
|
+
until @f.eof? || (l = @f.gets) =~ BREAK_RE
|
89
|
+
string << l
|
90
|
+
end
|
91
|
+
self.cur_offset += string.length
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
84
95
|
def raw_header offset
|
85
96
|
ret = ""
|
86
97
|
@mutex.synchronize do
|
87
98
|
@f.seek offset
|
88
99
|
until @f.eof? || (l = @f.gets) =~ /^\r*$/
|
89
|
-
ret
|
100
|
+
ret << l
|
90
101
|
end
|
91
102
|
end
|
92
103
|
ret
|
@@ -94,7 +105,7 @@ class Loader < Source
|
|
94
105
|
|
95
106
|
def raw_message offset
|
96
107
|
ret = ""
|
97
|
-
each_raw_message_line(offset) { |l| ret
|
108
|
+
each_raw_message_line(offset) { |l| ret << l }
|
98
109
|
ret
|
99
110
|
end
|
100
111
|
|
@@ -108,7 +119,7 @@ class Loader < Source
|
|
108
119
|
@mutex.synchronize do
|
109
120
|
@f.seek offset
|
110
121
|
yield @f.gets
|
111
|
-
until @f.eof? || (l = @f.gets)
|
122
|
+
until @f.eof? || MBox::is_break_line?(l = @f.gets)
|
112
123
|
yield l
|
113
124
|
end
|
114
125
|
end
|
@@ -129,7 +140,7 @@ class Loader < Source
|
|
129
140
|
## 2. at the beginning of an mbox separator (in all other
|
130
141
|
## cases).
|
131
142
|
|
132
|
-
l = @f.gets or
|
143
|
+
l = @f.gets or return nil
|
133
144
|
if l =~ /^\s*$/ # case 1
|
134
145
|
returned_offset = @f.tell
|
135
146
|
@f.gets # now we're at a BREAK_RE, so skip past it
|
@@ -139,7 +150,7 @@ class Loader < Source
|
|
139
150
|
end
|
140
151
|
|
141
152
|
while(line = @f.gets)
|
142
|
-
break if line
|
153
|
+
break if MBox::is_break_line? line
|
143
154
|
next_offset = @f.tell
|
144
155
|
end
|
145
156
|
end
|
data/lib/sup/message-chunks.rb
CHANGED
@@ -45,7 +45,10 @@ module Chunk
|
|
45
45
|
|
46
46
|
class Attachment
|
47
47
|
HookManager.register "mime-decode", <<EOS
|
48
|
-
|
48
|
+
Decodes a MIME attachment into text form. The text will be displayed
|
49
|
+
directly in Sup. For attachments that you wish to use a separate program
|
50
|
+
to view (e.g. images), you should use the mime-view hook instead.
|
51
|
+
|
49
52
|
Variables:
|
50
53
|
content_type: the content-type of the message
|
51
54
|
filename: the filename of the attachment as saved to disk
|
@@ -57,13 +60,22 @@ Return value:
|
|
57
60
|
EOS
|
58
61
|
|
59
62
|
HookManager.register "mime-view", <<EOS
|
60
|
-
|
61
|
-
|
63
|
+
Views a non-text MIME attachment. This hook allows you to run
|
64
|
+
third-party programs for attachments that require such a thing (e.g.
|
65
|
+
images). To instead display a text version of the attachment directly in
|
66
|
+
Sup, use the mime-decode hook instead.
|
67
|
+
|
68
|
+
Note that by default (at least on systems that have a run-mailcap command),
|
69
|
+
Sup uses the default mailcap handler for the attachment's MIME type. If
|
70
|
+
you want a particular behavior to be global, you may wish to change your
|
71
|
+
mailcap instead.
|
72
|
+
|
62
73
|
Variables:
|
63
74
|
content_type: the content-type of the attachment
|
64
75
|
filename: the filename of the attachment as saved to disk
|
65
76
|
Return value:
|
66
|
-
True if the viewing was successful, false otherwise.
|
77
|
+
True if the viewing was successful, false otherwise. If false, calling
|
78
|
+
/usr/bin/run-mailcap will be tried.
|
67
79
|
EOS
|
68
80
|
#' stupid ruby-mode
|
69
81
|
|
@@ -87,7 +99,7 @@ EOS
|
|
87
99
|
text =
|
88
100
|
case @content_type
|
89
101
|
when /^text\/plain\b/
|
90
|
-
|
102
|
+
Iconv.easy_decode $encoding, encoded_content.charset || $encoding, @raw_content
|
91
103
|
else
|
92
104
|
HookManager.run "mime-decode", :content_type => content_type,
|
93
105
|
:filename => lambda { write_to_disk },
|
@@ -108,7 +120,7 @@ EOS
|
|
108
120
|
if expandable?
|
109
121
|
"Attachment: #{filename} (#{lines.length} lines)"
|
110
122
|
else
|
111
|
-
"Attachment: #{filename} (#{content_type})"
|
123
|
+
"Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
|
112
124
|
end
|
113
125
|
end
|
114
126
|
|
data/lib/sup/message.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
require 'time'
|
2
|
-
require 'iconv'
|
3
2
|
|
4
3
|
module Redwood
|
5
4
|
|
6
|
-
class MessageFormatError < StandardError; end
|
7
|
-
|
8
5
|
## a Message is what's threaded.
|
9
6
|
##
|
10
7
|
## it is also where the parsing for quotes and signatures is done, but
|
@@ -13,8 +10,8 @@ class MessageFormatError < StandardError; end
|
|
13
10
|
## specific module that would detect and link to /ruby-talk:\d+/
|
14
11
|
## sequences in the text of an email. (how sweet would that be?)
|
15
12
|
##
|
16
|
-
## this class
|
17
|
-
## an error, it is caught and handled.
|
13
|
+
## this class catches all source exceptions. if the underlying source
|
14
|
+
## throws an error, it is caught and handled.
|
18
15
|
|
19
16
|
class Message
|
20
17
|
SNIPPET_LEN = 80
|
@@ -50,7 +47,7 @@ class Message
|
|
50
47
|
@snippet = opts[:snippet]
|
51
48
|
@snippet_contains_encrypted_content = false
|
52
49
|
@have_snippet = !(opts[:snippet].nil? || opts[:snippet].empty?)
|
53
|
-
@labels =
|
50
|
+
@labels = (opts[:labels] || []).to_set_of_symbols
|
54
51
|
@dirty = false
|
55
52
|
@encrypted = false
|
56
53
|
@chunks = nil
|
@@ -60,54 +57,54 @@ class Message
|
|
60
57
|
## why.
|
61
58
|
@refs = []
|
62
59
|
|
63
|
-
parse_header(opts[:header] || @source.load_header(@source_info))
|
60
|
+
#parse_header(opts[:header] || @source.load_header(@source_info))
|
64
61
|
end
|
65
62
|
|
66
63
|
def parse_header header
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if header["message-id"]
|
74
|
-
sanitize_message_id header["message-id"]
|
75
|
-
else
|
76
|
-
fakeid = "sup-faked-" + Digest::MD5.hexdigest(raw_header)
|
77
|
-
end
|
78
|
-
|
79
|
-
@from =
|
80
|
-
if header["from"]
|
81
|
-
PersonManager.person_for header["from"]
|
82
|
-
else
|
83
|
-
fakename = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
|
84
|
-
PersonManager.person_for fakename
|
85
|
-
end
|
64
|
+
## forcibly decode these headers from and to the current encoding,
|
65
|
+
## which serves to strip out characters that aren't displayable
|
66
|
+
## (and which would otherwise be screwing up the display)
|
67
|
+
%w(from to subject cc bcc).each do |f|
|
68
|
+
header[f] = Iconv.easy_decode($encoding, $encoding, header[f]) if header[f]
|
69
|
+
end
|
86
70
|
|
87
|
-
|
88
|
-
|
71
|
+
@id = if header["message-id"]
|
72
|
+
mid = header["message-id"] =~ /<(.+?)>/ ? $1 : header["message-id"]
|
73
|
+
sanitize_message_id mid
|
74
|
+
else
|
75
|
+
id = "sup-faked-" + Digest::MD5.hexdigest(raw_header)
|
76
|
+
from = header["from"]
|
77
|
+
#Redwood::log "faking non-existent message-id for message from #{from}: #{id}"
|
78
|
+
id
|
79
|
+
end
|
89
80
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
81
|
+
@from = Person.from_address(if header["from"]
|
82
|
+
header["from"]
|
83
|
+
else
|
84
|
+
name = "Sup Auto-generated Fake Sender <sup@fake.sender.example.com>"
|
85
|
+
#Redwood::log "faking non-existent sender for message #@id: #{name}"
|
86
|
+
name
|
87
|
+
end)
|
88
|
+
|
89
|
+
@date = case(date = header["date"])
|
90
|
+
when Time
|
91
|
+
date
|
92
|
+
when String
|
93
|
+
begin
|
94
|
+
Time.parse date
|
95
|
+
rescue ArgumentError => e
|
96
|
+
#Redwood::log "faking mangled date header for #{@id} (orig #{header['date'].inspect} gave error: #{e.message})"
|
104
97
|
Time.now
|
105
98
|
end
|
99
|
+
else
|
100
|
+
#Redwood::log "faking non-existent date header for #{@id}"
|
101
|
+
Time.now
|
102
|
+
end
|
106
103
|
|
107
104
|
@subj = header.member?("subject") ? header["subject"].gsub(/\s+/, " ").gsub(/\s+$/, "") : DEFAULT_SUBJECT
|
108
|
-
@to =
|
109
|
-
@cc =
|
110
|
-
@bcc =
|
105
|
+
@to = Person.from_address_list header["to"]
|
106
|
+
@cc = Person.from_address_list header["cc"]
|
107
|
+
@bcc = Person.from_address_list header["bcc"]
|
111
108
|
|
112
109
|
## before loading our full header from the source, we can actually
|
113
110
|
## have some extra refs set by the UI. (this happens when the user
|
@@ -117,10 +114,10 @@ class Message
|
|
117
114
|
@refs = (@refs + refs).uniq
|
118
115
|
@replytos = (header["in-reply-to"] || "").scan(/<(.+?)>/).map { |x| sanitize_message_id x.first }
|
119
116
|
|
120
|
-
@replyto =
|
117
|
+
@replyto = Person.from_address header["reply-to"]
|
121
118
|
@list_address =
|
122
119
|
if header["list-post"]
|
123
|
-
@list_address =
|
120
|
+
@list_address = Person.from_address header["list-post"].gsub(/^<mailto:|>$/, "")
|
124
121
|
else
|
125
122
|
nil
|
126
123
|
end
|
@@ -130,7 +127,6 @@ class Message
|
|
130
127
|
@list_subscribe = header["list-subscribe"]
|
131
128
|
@list_unsubscribe = header["list-unsubscribe"]
|
132
129
|
end
|
133
|
-
private :parse_header
|
134
130
|
|
135
131
|
def add_ref ref
|
136
132
|
@refs << ref
|
@@ -172,7 +168,7 @@ class Message
|
|
172
168
|
def has_label? t; @labels.member? t; end
|
173
169
|
def add_label t
|
174
170
|
return if @labels.member? t
|
175
|
-
@labels
|
171
|
+
@labels = (@labels + [t]).to_set_of_symbols
|
176
172
|
@dirty = true
|
177
173
|
end
|
178
174
|
def remove_label t
|
@@ -186,7 +182,7 @@ class Message
|
|
186
182
|
end
|
187
183
|
|
188
184
|
def labels= l
|
189
|
-
@labels = l
|
185
|
+
@labels = l.to_set_of_symbols
|
190
186
|
@dirty = true
|
191
187
|
end
|
192
188
|
|
@@ -198,7 +194,7 @@ class Message
|
|
198
194
|
## this is called when the message body needs to actually be loaded.
|
199
195
|
def load_from_source!
|
200
196
|
@chunks ||=
|
201
|
-
if @source.has_errors?
|
197
|
+
if @source.respond_to?(:has_errors?) && @source.has_errors?
|
202
198
|
[Chunk::Text.new(error_message(@source.error.message).split("\n"))]
|
203
199
|
else
|
204
200
|
begin
|
@@ -212,7 +208,7 @@ class Message
|
|
212
208
|
## so i will keep this.
|
213
209
|
parse_header @source.load_header(@source_info)
|
214
210
|
message_to_chunks @source.load_message(@source_info)
|
215
|
-
rescue SourceError, SocketError
|
211
|
+
rescue SourceError, SocketError => e
|
216
212
|
Redwood::log "problem getting messages from #{@source}: #{e.message}"
|
217
213
|
## we need force_to_top here otherwise this window will cover
|
218
214
|
## up the error message one
|
@@ -372,6 +368,7 @@ private
|
|
372
368
|
[notice, sig, children].flatten.compact
|
373
369
|
end
|
374
370
|
|
371
|
+
## takes a RMail::Message, breaks it into Chunk:: classes.
|
375
372
|
def message_to_chunks m, encrypted=false, sibling_types=[]
|
376
373
|
if m.multipart?
|
377
374
|
chunks =
|
@@ -391,7 +388,7 @@ private
|
|
391
388
|
elsif m.header.content_type == "message/rfc822"
|
392
389
|
payload = RMail::Parser.read(m.body)
|
393
390
|
from = payload.header.from.first
|
394
|
-
from_person = from ?
|
391
|
+
from_person = from ? Person.from_address(from.format) : nil
|
395
392
|
[Chunk::EnclosedMessage.new(from_person, payload.to_s)] +
|
396
393
|
message_to_chunks(payload, encrypted)
|
397
394
|
else
|
@@ -423,28 +420,20 @@ private
|
|
423
420
|
# Lowercase the filename because searches are easier that way
|
424
421
|
@attachments.push filename.downcase unless filename =~ /^sup-attachment-/
|
425
422
|
add_label :attachment unless filename =~ /^sup-attachment-/
|
426
|
-
|
423
|
+
content_type = m.header.content_type || "application/unknown" # sometimes RubyMail gives us nil
|
424
|
+
[Chunk::Attachment.new(content_type, filename, m, sibling_types)]
|
427
425
|
|
428
426
|
## otherwise, it's body text
|
429
427
|
else
|
430
|
-
|
428
|
+
## if there's no charset, use the current encoding as the charset.
|
429
|
+
## this ensures that the body is normalized to avoid non-displayable
|
430
|
+
## characters
|
431
|
+
body = Iconv.easy_decode($encoding, m.charset || $encoding, m.decode) if m.body
|
431
432
|
text_to_chunks((body || "").normalize_whitespace.split("\n"), encrypted)
|
432
433
|
end
|
433
434
|
end
|
434
435
|
end
|
435
436
|
|
436
|
-
def self.convert_from body, charset
|
437
|
-
begin
|
438
|
-
raise MessageFormatError, "RubyMail decode returned a null body" unless body
|
439
|
-
return body unless charset
|
440
|
-
Iconv.easy_decode($encoding, charset, body)
|
441
|
-
rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence, MessageFormatError => e
|
442
|
-
Redwood::log "warning: error (#{e.class.name}) decoding message body from #{charset}: #{e.message}"
|
443
|
-
File.open(File.join(BASE_DIR,"unable-to-decode.txt"), "w") { |f| f.write body }
|
444
|
-
body
|
445
|
-
end
|
446
|
-
end
|
447
|
-
|
448
437
|
## parse the lines of text into chunk objects. the heuristics here
|
449
438
|
## need tweaking in some nice manner. TODO: move these heuristics
|
450
439
|
## into the classes themselves.
|