sup 0.5 → 0.6
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 -0
- data/History.txt +10 -0
- data/Manifest.txt +1 -1
- data/Rakefile +3 -3
- data/ReleaseNotes +6 -0
- data/bin/sup +18 -58
- data/lib/sup/colormap.rb +94 -2
- data/lib/sup/crypto.rb +3 -1
- data/lib/sup/draft.rb +1 -1
- data/lib/sup/hook.rb +1 -1
- data/lib/sup/index.rb +16 -1
- data/lib/sup/keymap.rb +1 -7
- data/lib/sup/label.rb +3 -3
- data/lib/sup/maildir.rb +35 -17
- data/lib/sup/mbox.rb +18 -17
- data/lib/sup/message.rb +20 -3
- data/lib/sup/modes/compose-mode.rb +2 -0
- data/lib/sup/modes/edit-message-mode.rb +13 -12
- data/lib/sup/modes/file-browser-mode.rb +1 -1
- data/lib/sup/modes/forward-mode.rb +1 -1
- data/lib/sup/modes/inbox-mode.rb +18 -0
- data/lib/sup/modes/label-list-mode.rb +5 -0
- data/lib/sup/modes/reply-mode.rb +38 -2
- data/lib/sup/modes/scroll-mode.rb +11 -6
- data/lib/sup/modes/thread-index-mode.rb +11 -4
- data/lib/sup/modes/thread-view-mode.rb +9 -9
- data/lib/sup/util.rb +6 -2
- data/lib/sup.rb +30 -6
- data/test/test_mbox_parsing.rb +114 -0
- metadata +34 -5
- data/doc/TODO +0 -197
- data/test/test_maildir.rb +0 -25
data/CONTRIBUTORS
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
William Morgan <wmorgan-sup at the masanjin dot nets>
|
2
|
+
Ismo Puustinen <ismo at the iki dot fis>
|
3
|
+
Marcus Williams <marcus-sup at the bar-coded dot nets>
|
4
|
+
Lionel Ott <white.magic at the gmx dot des>
|
5
|
+
Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
|
6
|
+
Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
|
7
|
+
Ben Walton <bwalton at the artsci.utoronto dot cas>
|
8
|
+
Grant Hollingworth <grant at the antiflux dot orgs>
|
9
|
+
Jeff Balogh <its.jeff.balogh at the gmail dot coms>
|
10
|
+
Christopher Warrington <chrisw at the rice dot edus>
|
11
|
+
Giorgio Lando <patroclo7 at the gmail dot coms>
|
12
|
+
Decklin Foster <decklin at the red-bean dot coms>
|
13
|
+
Ian Taylor <ian at the lorf dot orgs>
|
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
== 0.6 / 2008-08-04
|
2
|
+
* new hooks: mark-as-spam, reply-to, reply-from
|
3
|
+
* configurable colors. finally!
|
4
|
+
* many bugfixes
|
5
|
+
* more vi keys added, and 'q' now asks before quitting
|
6
|
+
* attachment markers (little @ signs!) in thread-index-mode
|
7
|
+
* maildir speedups
|
8
|
+
* attachment name searchability
|
9
|
+
* archive-and-mark-read command in inbox-mode
|
10
|
+
|
1
11
|
== 0.5 / 2008-04-22
|
2
12
|
* new hooks: extra-contact-addresses, startup
|
3
13
|
* '!!' now loads all threads in current search
|
data/Manifest.txt
CHANGED
data/Rakefile
CHANGED
@@ -24,7 +24,7 @@ Hoe.new('sup', version) do |p|
|
|
24
24
|
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2].gsub(/^\s+/, "")
|
25
25
|
p.changes = p.paragraphs_of('History.txt', 0..0).join("\n\n")
|
26
26
|
p.email = "wmorgan-sup@masanjin.net"
|
27
|
-
p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh', ['trollop', '>= 1.7'], 'lockfile', 'mime-types', 'gettext']
|
27
|
+
p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh', ['trollop', '>= 1.7'], 'lockfile', 'mime-types', 'gettext', 'fastthread']
|
28
28
|
end
|
29
29
|
|
30
30
|
rule 'ss?.png' => 'ss?-small.png' do |t|
|
@@ -45,11 +45,11 @@ SCREENSHOTS.each do |fn|
|
|
45
45
|
end
|
46
46
|
|
47
47
|
task :upload_webpage => WWW_FILES do |t|
|
48
|
-
sh "
|
48
|
+
sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
|
49
49
|
end
|
50
50
|
|
51
51
|
task :upload_webpage_images => (SCREENSHOTS + SCREENSHOTS_SMALL) do |t|
|
52
|
-
sh "
|
52
|
+
sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
|
53
53
|
end
|
54
54
|
|
55
55
|
# vim: syntax=ruby
|
data/ReleaseNotes
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
Release 0.6:
|
2
|
+
|
3
|
+
Message attachment searchability automatically takes effect on new messages,
|
4
|
+
but if you want it on older ones, you'll have to reindex them. See the
|
5
|
+
instructions below, and the help for sup-sync, for how to do this.
|
6
|
+
|
1
7
|
Release 0.5:
|
2
8
|
|
3
9
|
Saving message state (pressing "$") has been sped up. However, this is only
|
data/bin/sup
CHANGED
@@ -5,9 +5,10 @@ require 'ncurses'
|
|
5
5
|
require 'curses'
|
6
6
|
require 'fileutils'
|
7
7
|
require 'trollop'
|
8
|
+
require 'fastthread'
|
8
9
|
require "sup"
|
9
10
|
|
10
|
-
BIN_VERSION = "0.
|
11
|
+
BIN_VERSION = "0.6"
|
11
12
|
|
12
13
|
unless Redwood::VERSION == BIN_VERSION
|
13
14
|
$stderr.puts <<EOS
|
@@ -21,7 +22,6 @@ EOS
|
|
21
22
|
exit(-1)
|
22
23
|
end
|
23
24
|
|
24
|
-
$exceptions = []
|
25
25
|
$opts = Trollop::options do
|
26
26
|
version "sup v#{Redwood::VERSION}"
|
27
27
|
banner <<EOS
|
@@ -55,7 +55,8 @@ Thread.abort_on_exception = true # make debugging possible
|
|
55
55
|
module Redwood
|
56
56
|
|
57
57
|
global_keymap = Keymap.new do |k|
|
58
|
-
k.add :
|
58
|
+
k.add :quit_ask, "Quit Sup, but ask first", 'q'
|
59
|
+
k.add :quit_now, "Quit Sup immediately", 'Q'
|
59
60
|
k.add :help, "Show help", 'H', '?'
|
60
61
|
k.add :roll_buffers, "Switch to next buffer", 'b'
|
61
62
|
# k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
|
@@ -139,53 +140,8 @@ begin
|
|
139
140
|
log "starting curses"
|
140
141
|
start_cursing
|
141
142
|
|
142
|
-
Colormap.new do |c|
|
143
|
-
c.add :status_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLUE, Ncurses::A_BOLD
|
144
|
-
c.add :index_old_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK
|
145
|
-
c.add :index_new_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK,
|
146
|
-
Ncurses::A_BOLD
|
147
|
-
c.add :index_starred_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK,
|
148
|
-
Ncurses::A_BOLD
|
149
|
-
c.add :index_draft_color, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK,
|
150
|
-
Ncurses::A_BOLD
|
151
|
-
c.add :labellist_old_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK
|
152
|
-
c.add :labellist_new_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK,
|
153
|
-
Ncurses::A_BOLD
|
154
|
-
c.add :twiddle_color, Ncurses::COLOR_BLUE, Ncurses::COLOR_BLACK
|
155
|
-
c.add :label_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
|
156
|
-
c.add :message_patina_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_GREEN
|
157
|
-
c.add :alternate_patina_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_BLUE
|
158
|
-
c.add :missing_message_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_RED
|
159
|
-
c.add :attachment_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
|
160
|
-
c.add :cryptosig_valid_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
|
161
|
-
c.add :cryptosig_unknown_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
|
162
|
-
c.add :cryptosig_invalid_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_RED, Ncurses::A_BOLD
|
163
|
-
c.add :generic_notice_patina_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
|
164
|
-
c.add :quote_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
|
165
|
-
c.add :sig_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
|
166
|
-
c.add :quote_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
|
167
|
-
c.add :sig_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
|
168
|
-
c.add :to_me_color, Ncurses::COLOR_GREEN, Ncurses::COLOR_BLACK
|
169
|
-
c.add :starred_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK,
|
170
|
-
Ncurses::A_BOLD
|
171
|
-
c.add :starred_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_GREEN,
|
172
|
-
Ncurses::A_BOLD
|
173
|
-
c.add :alternate_starred_patina_color, Ncurses::COLOR_YELLOW,
|
174
|
-
Ncurses::COLOR_BLUE, Ncurses::A_BOLD
|
175
|
-
c.add :snippet_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
|
176
|
-
c.add :option_color, Ncurses::COLOR_WHITE, Ncurses::COLOR_BLACK
|
177
|
-
c.add :tagged_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK,
|
178
|
-
Ncurses::A_BOLD
|
179
|
-
c.add :draft_notification_color, Ncurses::COLOR_RED, Ncurses::COLOR_BLACK,
|
180
|
-
Ncurses::A_BOLD
|
181
|
-
c.add :completion_character_color, Ncurses::COLOR_WHITE,
|
182
|
-
Ncurses::COLOR_BLACK, Ncurses::A_BOLD
|
183
|
-
c.add :horizontal_selector_selected_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK, Ncurses::A_BOLD
|
184
|
-
c.add :horizontal_selector_unselected_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
|
185
|
-
c.add :search_highlight_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_YELLOW, Ncurses::A_BOLD, :highlight => :search_highlight_color
|
186
|
-
end
|
187
|
-
|
188
143
|
bm = BufferManager.new
|
144
|
+
Colormap.new.populate_colormap
|
189
145
|
|
190
146
|
log "initializing mail index buffer"
|
191
147
|
imode = InboxMode.new
|
@@ -223,7 +179,7 @@ begin
|
|
223
179
|
SearchResultsMode.spawn_from_query $opts[:search]
|
224
180
|
end
|
225
181
|
|
226
|
-
until
|
182
|
+
until Redwood::exceptions.nonempty? || SuicideManager.die?
|
227
183
|
c = Ncurses.nonblocking_getch
|
228
184
|
next unless c
|
229
185
|
bm.erase_flash
|
@@ -240,8 +196,12 @@ begin
|
|
240
196
|
end
|
241
197
|
|
242
198
|
case action
|
243
|
-
when :
|
199
|
+
when :quit_now
|
244
200
|
break if bm.kill_all_buffers_safely
|
201
|
+
when :quit_ask
|
202
|
+
if bm.ask_yes_or_no "Really quit?"
|
203
|
+
break if bm.kill_all_buffers_safely
|
204
|
+
end
|
245
205
|
when :help
|
246
206
|
curmode = bm.focus_buf.mode
|
247
207
|
bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
|
@@ -300,7 +260,7 @@ begin
|
|
300
260
|
|
301
261
|
bm.kill_all_buffers if SuicideManager.die?
|
302
262
|
rescue Exception => e
|
303
|
-
|
263
|
+
Redwood::record_exception e, "main"
|
304
264
|
ensure
|
305
265
|
unless $opts[:no_threads]
|
306
266
|
PollManager.stop if PollManager.instantiated?
|
@@ -316,7 +276,7 @@ ensure
|
|
316
276
|
Redwood::log "I've been ordered to commit seppuku. I obey!"
|
317
277
|
end
|
318
278
|
|
319
|
-
if
|
279
|
+
if Redwood::exceptions.empty?
|
320
280
|
Redwood::log "no fatal errors. good job, william."
|
321
281
|
Index.save
|
322
282
|
else
|
@@ -326,9 +286,9 @@ ensure
|
|
326
286
|
Index.unlock
|
327
287
|
end
|
328
288
|
|
329
|
-
unless
|
330
|
-
File.open("
|
331
|
-
|
289
|
+
unless Redwood::exceptions.empty?
|
290
|
+
File.open(File.join(BASE_DIR, "exception-log.txt"), "w") do |f|
|
291
|
+
Redwood::exceptions.each do |e, name|
|
332
292
|
f.puts "--- #{e.class.name} from thread: #{name}"
|
333
293
|
f.puts e.message, e.backtrace
|
334
294
|
end
|
@@ -337,7 +297,7 @@ unless $exceptions.empty?
|
|
337
297
|
----------------------------------------------------------------
|
338
298
|
I'm very sorry. It seems that an error occurred in Sup. Please
|
339
299
|
accept my sincere apologies. If you don't mind, please send the
|
340
|
-
contents of sup
|
300
|
+
contents of ~/.sup/exception-log.txt and a brief report of the
|
341
301
|
circumstances to sup-talk at rubyforge dot orgs so that I might
|
342
302
|
address this problem. Thank you!
|
343
303
|
|
@@ -345,7 +305,7 @@ Sincerely,
|
|
345
305
|
William
|
346
306
|
----------------------------------------------------------------
|
347
307
|
EOS
|
348
|
-
|
308
|
+
Redwood::exceptions.each do |e, name|
|
349
309
|
puts "--- #{e.class.name} from thread: #{name}"
|
350
310
|
puts e.message, e.backtrace
|
351
311
|
end
|
data/lib/sup/colormap.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
module Curses
|
2
|
+
COLOR_DEFAULT = -1
|
3
|
+
end
|
4
|
+
|
1
5
|
module Redwood
|
2
6
|
|
3
7
|
class Colormap
|
@@ -6,8 +10,44 @@ class Colormap
|
|
6
10
|
CURSES_COLORS = [Curses::COLOR_BLACK, Curses::COLOR_RED, Curses::COLOR_GREEN,
|
7
11
|
Curses::COLOR_YELLOW, Curses::COLOR_BLUE,
|
8
12
|
Curses::COLOR_MAGENTA, Curses::COLOR_CYAN,
|
9
|
-
Curses::COLOR_WHITE]
|
13
|
+
Curses::COLOR_WHITE, Curses::COLOR_DEFAULT]
|
10
14
|
NUM_COLORS = 15
|
15
|
+
|
16
|
+
DEFAULT_COLORS = {
|
17
|
+
:status => { :fg => "white", :bg => "blue", :attrs => ["bold"] },
|
18
|
+
:index_old => { :fg => "white", :bg => "black" },
|
19
|
+
:index_new => { :fg => "white", :bg => "black", :attrs => ["bold"] },
|
20
|
+
:index_starred => { :fg => "yellow", :bg => "black", :attrs => ["bold"] },
|
21
|
+
:index_draft => { :fg => "red", :bg => "black", :attrs => ["bold"] },
|
22
|
+
:labellist_old => { :fg => "white", :bg => "black" },
|
23
|
+
:labellist_new => { :fg => "white", :bg => "black", :attrs => ["bold"] },
|
24
|
+
:twiddle => { :fg => "blue", :bg => "black" },
|
25
|
+
:label => { :fg => "yellow", :bg => "black" },
|
26
|
+
:message_patina => { :fg => "black", :bg => "green" },
|
27
|
+
:alternate_patina => { :fg => "black", :bg => "blue" },
|
28
|
+
:missing_message => { :fg => "black", :bg => "red" },
|
29
|
+
:attachment => { :fg => "cyan", :bg => "black" },
|
30
|
+
:cryptosig_valid => { :fg => "yellow", :bg => "black", :attrs => ["bold"] },
|
31
|
+
:cryptosig_unknown => { :fg => "cyan", :bg => "black" },
|
32
|
+
:cryptosig_invalid => { :fg => "yellow", :bg => "red", :attrs => ["bold"] },
|
33
|
+
:generic_notice_patina => { :fg => "cyan", :bg => "black" },
|
34
|
+
:quote_patina => { :fg => "yellow", :bg => "black" },
|
35
|
+
:sig_patina => { :fg => "yellow", :bg => "black" },
|
36
|
+
:quote => { :fg => "yellow", :bg => "black" },
|
37
|
+
:sig => { :fg => "yellow", :bg => "black" },
|
38
|
+
:to_me => { :fg => "green", :bg => "black" },
|
39
|
+
:starred => { :fg => "yellow", :bg => "black", :attrs => ["bold"] },
|
40
|
+
:starred_patina => { :fg => "yellow", :bg => "green", :attrs => ["bold"] },
|
41
|
+
:alternate_starred_patina => { :fg => "yellow", :bg => "blue", :attrs => ["bold"] },
|
42
|
+
:snippet => { :fg => "cyan", :bg => "black" },
|
43
|
+
:option => { :fg => "white", :bg => "black" },
|
44
|
+
:tagged => { :fg => "yellow", :bg => "black", :attrs => ["bold"] },
|
45
|
+
:draft_notification => { :fg => "red", :bg => "black", :attrs => ["bold"] },
|
46
|
+
:completion_character => { :fg => "white", :bg => "black", :attrs => ["bold"] },
|
47
|
+
:horizontal_selector_selected => { :fg => "yellow", :bg => "black", :attrs => ["bold"] },
|
48
|
+
:horizontal_selector_unselected => { :fg => "cyan", :bg => "black" },
|
49
|
+
:search_highlight => { :fg => "black", :bg => "yellow", :attrs => ["bold"] }
|
50
|
+
}
|
11
51
|
|
12
52
|
def initialize
|
13
53
|
raise "only one instance can be created" if @@instance
|
@@ -108,9 +148,61 @@ class Colormap
|
|
108
148
|
color
|
109
149
|
end
|
110
150
|
|
151
|
+
## Try to use the user defined colors, in case of an error fall back
|
152
|
+
## to the default ones.
|
153
|
+
def populate_colormap
|
154
|
+
user_colors = if File.exists? Redwood::COLOR_FN
|
155
|
+
Redwood::log "loading user colors from #{Redwood::COLOR_FN}"
|
156
|
+
Redwood::load_yaml_obj Redwood::COLOR_FN
|
157
|
+
end
|
158
|
+
|
159
|
+
error = nil
|
160
|
+
Colormap::DEFAULT_COLORS.each_pair do |k, v|
|
161
|
+
fg = Curses.const_get "COLOR_#{v[:fg].upcase}"
|
162
|
+
bg = Curses.const_get "COLOR_#{v[:bg].upcase}"
|
163
|
+
attrs = v[:attrs] ? v[:attrs].map { |a| Curses.const_get "A_#{a.upcase}" } : []
|
164
|
+
|
165
|
+
if user_colors && (ucolor = user_colors[k])
|
166
|
+
if(ufg = ucolor[:fg])
|
167
|
+
begin
|
168
|
+
fg = Curses.const_get "COLOR_#{ufg.upcase}"
|
169
|
+
rescue NameError
|
170
|
+
error ||= "Warning: there is no color named \"#{ufg}\", using fallback."
|
171
|
+
Redwood::log "Warning: there is no color named \"#{ufg}\""
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
if(ubg = ucolor[:bg])
|
176
|
+
begin
|
177
|
+
bg = Curses.const_get "COLOR_#{ubg.upcase}"
|
178
|
+
rescue NameError
|
179
|
+
error ||= "Warning: there is no color named \"#{ubg}\", using fallback."
|
180
|
+
Redwood::log "Warning: there is no color named \"#{ubg}\""
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
if(uattrs = ucolor[:attrs])
|
185
|
+
attrs = [*uattrs].flatten.map do |a|
|
186
|
+
begin
|
187
|
+
Curses.const_get "A_#{a.upcase}"
|
188
|
+
rescue NameError
|
189
|
+
error ||= "Warning: there is no attribute named \"#{a}\", using fallback."
|
190
|
+
Redwood::log "Warning: there is no attribute named \"#{a}\", using fallback."
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
symbol = (k.to_s + "_color").to_sym
|
197
|
+
add symbol, fg, bg, attrs
|
198
|
+
end
|
199
|
+
|
200
|
+
BufferManager.flash error if error
|
201
|
+
end
|
202
|
+
|
111
203
|
def self.instance; @@instance; end
|
112
204
|
def self.method_missing meth, *a
|
113
|
-
|
205
|
+
Colormap.new unless @@instance
|
114
206
|
@@instance.send meth, *a
|
115
207
|
end
|
116
208
|
end
|
data/lib/sup/crypto.rb
CHANGED
@@ -53,7 +53,7 @@ class CryptoManager
|
|
53
53
|
payload_fn.write format_payload(payload)
|
54
54
|
payload_fn.close
|
55
55
|
|
56
|
-
recipient_opts = to.map { |r| "--recipient '
|
56
|
+
recipient_opts = to.map { |r| "--recipient '<#{r}>'" }.join(" ")
|
57
57
|
sign_opts = sign ? "--sign --local-user '#{from}'" : ""
|
58
58
|
gpg_output = run_gpg "--output - --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}"
|
59
59
|
raise Error, (gpg_output || "gpg command failed: #{cmd}") unless $?.success?
|
@@ -150,6 +150,8 @@ private
|
|
150
150
|
["Can't find gpg binary in path."]
|
151
151
|
end
|
152
152
|
|
153
|
+
## here's where we munge rmail output into the format that signed/encrypted
|
154
|
+
## PGP/GPG messages should be
|
153
155
|
def format_payload payload
|
154
156
|
payload.to_s.gsub(/(^|[^\r])\n/, "\\1\r\n").gsub(/^MIME-Version: .*\r\n/, "")
|
155
157
|
end
|
data/lib/sup/draft.rb
CHANGED
data/lib/sup/hook.rb
CHANGED
data/lib/sup/index.rb
CHANGED
@@ -147,6 +147,7 @@ EOS
|
|
147
147
|
field_infos.add_field :date, :index => :untokenized
|
148
148
|
field_infos.add_field :body
|
149
149
|
field_infos.add_field :label
|
150
|
+
field_infos.add_field :attachments
|
150
151
|
field_infos.add_field :subject
|
151
152
|
field_infos.add_field :from
|
152
153
|
field_infos.add_field :to
|
@@ -223,6 +224,7 @@ EOS
|
|
223
224
|
:body => (entry[:body] || m.indexable_content),
|
224
225
|
:snippet => snippet, # always override
|
225
226
|
:label => labels.uniq.join(" "),
|
227
|
+
:attachments => (entry[:attachments] || m.attachments.uniq.join(" ")),
|
226
228
|
:from => (entry[:from] || (m.from ? m.from.indexable_content : "")),
|
227
229
|
:to => (entry[:to] || (m.to + m.cc + m.bcc).map { |x| x.indexable_content }.join(" ")),
|
228
230
|
:subject => (entry[:subject] || wrap_subj(Message.normalize_subj(m.subj))),
|
@@ -465,7 +467,7 @@ protected
|
|
465
467
|
extraopts[:load_deleted] = true if subs =~ /\blabel:deleted\b/
|
466
468
|
|
467
469
|
## gmail style "is" operator
|
468
|
-
subs = subs.gsub(/\b(is):(\S+)\b/) do
|
470
|
+
subs = subs.gsub(/\b(is|has):(\S+)\b/) do
|
469
471
|
field, label = $1, $2
|
470
472
|
case label
|
471
473
|
when "read"
|
@@ -481,6 +483,19 @@ protected
|
|
481
483
|
end
|
482
484
|
end
|
483
485
|
|
486
|
+
## gmail style attachments "filename" and "filetype" searches
|
487
|
+
subs = subs.gsub(/\b(filename|filetype):(\((.+?)\)\B|(\S+)\b)/) do
|
488
|
+
field, name = $1, ($3 || $4)
|
489
|
+
case field
|
490
|
+
when "filename"
|
491
|
+
Redwood::log "filename - translated #{field}:#{name} to attachments:(#{name.downcase})"
|
492
|
+
"attachments:(#{name.downcase})"
|
493
|
+
when "filetype"
|
494
|
+
Redwood::log "filetype - translated #{field}:#{name} to attachments:(*.#{name.downcase})"
|
495
|
+
"attachments:(*.#{name.downcase})"
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
484
499
|
if $have_chronic
|
485
500
|
chronic_failure = false
|
486
501
|
subs = subs.gsub(/\b(before|on|in|during|after):(\((.+?)\)\B|(\S+)\b)/) do
|
data/lib/sup/keymap.rb
CHANGED
@@ -43,16 +43,10 @@ class Keymap
|
|
43
43
|
when :home: "<home>"
|
44
44
|
when :end: "<end>"
|
45
45
|
when :enter, :return: "<enter>"
|
46
|
-
when :ctrl_l: "ctrl-l"
|
47
|
-
when :ctrl_g: "ctrl-g"
|
48
46
|
when :tab: "tab"
|
49
47
|
when " ": "<space>"
|
50
48
|
else
|
51
|
-
|
52
|
-
k
|
53
|
-
else
|
54
|
-
raise ArgumentError, "unknown key name \"#{k}\""
|
55
|
-
end
|
49
|
+
Curses::keyname(keysym_to_keycode(k))
|
56
50
|
end
|
57
51
|
end
|
58
52
|
|
data/lib/sup/label.rb
CHANGED
@@ -5,13 +5,13 @@ class LabelManager
|
|
5
5
|
|
6
6
|
## labels that have special semantics. user will be unable to
|
7
7
|
## add/remove these via normal label mechanisms.
|
8
|
-
RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox ]
|
8
|
+
RESERVED_LABELS = [ :starred, :spam, :draft, :unread, :killed, :sent, :deleted, :inbox, :attachment ]
|
9
9
|
|
10
10
|
## labels which it nonetheless makes sense to search for by
|
11
|
-
LISTABLE_RESERVED_LABELS = [ :starred, :spam, :draft, :sent, :killed, :deleted, :inbox ]
|
11
|
+
LISTABLE_RESERVED_LABELS = [ :starred, :spam, :draft, :sent, :killed, :deleted, :inbox, :attachment ]
|
12
12
|
|
13
13
|
## labels that will typically be hidden from the user
|
14
|
-
HIDDEN_RESERVED_LABELS = [ :starred, :unread ]
|
14
|
+
HIDDEN_RESERVED_LABELS = [ :starred, :unread, :attachment ]
|
15
15
|
|
16
16
|
def initialize fn
|
17
17
|
@fn = fn
|
data/lib/sup/maildir.rb
CHANGED
@@ -12,14 +12,14 @@ class Maildir < Source
|
|
12
12
|
SCAN_INTERVAL = 30 # seconds
|
13
13
|
|
14
14
|
## remind me never to use inheritance again.
|
15
|
-
yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels
|
16
|
-
def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[]
|
15
|
+
yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels, :mtimes
|
16
|
+
def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[], mtimes={}
|
17
17
|
super uri, last_date, usual, archived, id
|
18
18
|
uri = URI(Source.expand_filesystem_uri(uri))
|
19
19
|
|
20
20
|
raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
|
21
21
|
raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
|
22
|
-
raise ArgumentError, "
|
22
|
+
raise ArgumentError, "maildir URI must have a path component" unless uri.path
|
23
23
|
|
24
24
|
@dir = uri.path
|
25
25
|
@labels = (labels || []).freeze
|
@@ -27,6 +27,11 @@ class Maildir < Source
|
|
27
27
|
@ids_to_fns = {}
|
28
28
|
@last_scan = nil
|
29
29
|
@mutex = Mutex.new
|
30
|
+
#the mtime from the subdirs in the maildir with the unix epoch as default.
|
31
|
+
#these are used to determine whether scanning the directory for new mail
|
32
|
+
#is a worthwhile effort
|
33
|
+
@mtimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }.merge(mtimes || {})
|
34
|
+
@dir_ids = { 'cur' => [], 'new' => [] }
|
30
35
|
end
|
31
36
|
|
32
37
|
def file_path; @dir end
|
@@ -72,28 +77,38 @@ class Maildir < Source
|
|
72
77
|
|
73
78
|
def raw_message id
|
74
79
|
scan_mailbox
|
75
|
-
with_file_for(id) { |f| f.
|
80
|
+
with_file_for(id) { |f| f.read }
|
76
81
|
end
|
77
82
|
|
78
83
|
def scan_mailbox opts={}
|
79
84
|
return unless @ids.empty? || opts[:rescan]
|
80
85
|
return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
|
81
86
|
|
82
|
-
|
83
|
-
cdir = File.join(@dir, 'cur')
|
84
|
-
ndir = File.join(@dir, 'new')
|
85
|
-
|
86
|
-
raise FatalSourceError, "#{cdir} not a directory" unless File.directory? cdir
|
87
|
-
raise FatalSourceError, "#{ndir} not a directory" unless File.directory? ndir
|
87
|
+
initial_poll = @ids.empty?
|
88
88
|
|
89
|
+
Redwood::log "scanning maildir #@dir..."
|
89
90
|
begin
|
90
|
-
@
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
91
|
+
@mtimes.each_key do |d|
|
92
|
+
subdir = File.join(@dir, d)
|
93
|
+
raise FatalSourceError, "#{subdir} not a directory" unless File.directory? subdir
|
94
|
+
|
95
|
+
mtime = File.mtime subdir
|
96
|
+
|
97
|
+
#only scan the dir if the mtime is more recent (or we haven't polled
|
98
|
+
#since startup)
|
99
|
+
if @mtimes[d] < mtime || initial_poll
|
100
|
+
@mtimes[d] = mtime
|
101
|
+
@dir_ids[d] = []
|
102
|
+
Dir[File.join(subdir, '*')].map do |fn|
|
103
|
+
id = make_id fn
|
104
|
+
@dir_ids[d] << id
|
105
|
+
@ids_to_fns[id] = fn
|
106
|
+
end
|
107
|
+
else
|
108
|
+
Redwood::log "no poll on #{d}. mtime on indicates no new messages."
|
109
|
+
end
|
95
110
|
end
|
96
|
-
@ids.sort!
|
111
|
+
@ids = @dir_ids.values.flatten.uniq.sort!
|
97
112
|
rescue SystemCallError, IOError => e
|
98
113
|
raise FatalSourceError, "Problem scanning Maildir directories: #{e.message}."
|
99
114
|
end
|
@@ -145,8 +160,11 @@ class Maildir < Source
|
|
145
160
|
private
|
146
161
|
|
147
162
|
def make_id fn
|
163
|
+
#doing this means 1 syscall instead of 2 (File.mtime, File.size).
|
164
|
+
#makes a noticeable difference on nfs.
|
165
|
+
stat = File.stat(fn)
|
148
166
|
# use 7 digits for the size. why 7? seems nice.
|
149
|
-
sprintf("%d%07d",
|
167
|
+
sprintf("%d%07d", stat.mtime, stat.size % 10000000).to_i
|
150
168
|
end
|
151
169
|
|
152
170
|
def with_file_for id
|
data/lib/sup/mbox.rb
CHANGED
@@ -11,6 +11,7 @@ module Redwood
|
|
11
11
|
## TODO: move functionality to somewhere better, like message.rb
|
12
12
|
module MBox
|
13
13
|
BREAK_RE = /^From \S+/
|
14
|
+
HEADER_RE = /\s*(.*?\S)\s*/
|
14
15
|
|
15
16
|
def read_header f
|
16
17
|
header = {}
|
@@ -20,26 +21,26 @@ module MBox
|
|
20
21
|
## when scanning over large mbox files.
|
21
22
|
while(line = f.gets)
|
22
23
|
case line
|
23
|
-
when /^(From)
|
24
|
-
/^(To)
|
25
|
-
/^(Cc)
|
26
|
-
/^(Bcc)
|
27
|
-
/^(Subject)
|
28
|
-
/^(Date)
|
29
|
-
/^(References)
|
30
|
-
/^(In-Reply-To)
|
31
|
-
/^(Reply-To)
|
32
|
-
/^(List-Post)
|
33
|
-
/^(List-Subscribe)
|
34
|
-
/^(List-Unsubscribe)
|
35
|
-
/^(Status)
|
36
|
-
when /^(Message-Id)
|
24
|
+
when /^(From):#{HEADER_RE}$/i,
|
25
|
+
/^(To):#{HEADER_RE}$/i,
|
26
|
+
/^(Cc):#{HEADER_RE}$/i,
|
27
|
+
/^(Bcc):#{HEADER_RE}$/i,
|
28
|
+
/^(Subject):#{HEADER_RE}$/i,
|
29
|
+
/^(Date):#{HEADER_RE}$/i,
|
30
|
+
/^(References):#{HEADER_RE}$/i,
|
31
|
+
/^(In-Reply-To):#{HEADER_RE}$/i,
|
32
|
+
/^(Reply-To):#{HEADER_RE}$/i,
|
33
|
+
/^(List-Post):#{HEADER_RE}$/i,
|
34
|
+
/^(List-Subscribe):#{HEADER_RE}$/i,
|
35
|
+
/^(List-Unsubscribe):#{HEADER_RE}$/i,
|
36
|
+
/^(Status):#{HEADER_RE}$/i: header[last = $1] = $2
|
37
|
+
when /^(Message-Id):#{HEADER_RE}$/i: header[mid_field = last = $1] = $2
|
37
38
|
|
38
39
|
## these next three can occur multiple times, and we want the
|
39
40
|
## first one
|
40
|
-
when /^(Delivered-To)
|
41
|
-
/^(X-Original-To)
|
42
|
-
/^(Envelope-To)
|
41
|
+
when /^(Delivered-To):#{HEADER_RE}$/i,
|
42
|
+
/^(X-Original-To):#{HEADER_RE}$/i,
|
43
|
+
/^(Envelope-To):#{HEADER_RE}$/i: header[last = $1] ||= $2
|
43
44
|
|
44
45
|
when /^\r*$/: break
|
45
46
|
when /^\S+:/: last = nil # some other header we don't care about
|