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
@@ -0,0 +1,142 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class HookManager
|
4
|
+
class HookContext
|
5
|
+
def initialize name
|
6
|
+
@__say_id = nil
|
7
|
+
@__name = name
|
8
|
+
@__cache = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def say s
|
12
|
+
if BufferManager.instantiated?
|
13
|
+
@__say_id = BufferManager.say s, @__say_id
|
14
|
+
BufferManager.draw_screen
|
15
|
+
else
|
16
|
+
log s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def log s
|
21
|
+
info "hook[#@__name]: #{s}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def ask_yes_or_no q
|
25
|
+
if BufferManager.instantiated?
|
26
|
+
BufferManager.ask_yes_or_no q
|
27
|
+
else
|
28
|
+
print q
|
29
|
+
gets.chomp.downcase == 'y'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def get tag
|
34
|
+
HookManager.tags[tag]
|
35
|
+
end
|
36
|
+
|
37
|
+
def set tag, value
|
38
|
+
HookManager.tags[tag] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def __run __hook, __filename, __locals
|
42
|
+
__binding = binding
|
43
|
+
__lprocs, __lvars = __locals.partition { |k, v| v.is_a?(Proc) }
|
44
|
+
eval __lvars.map { |k, v| "#{k} = __locals[#{k.inspect}];" }.join, __binding
|
45
|
+
## we also support closures for delays evaluation. unfortunately
|
46
|
+
## we have to do this via method calls, so you don't get all the
|
47
|
+
## semantics of a regular variable. not ideal.
|
48
|
+
__lprocs.each do |k, v|
|
49
|
+
self.class.instance_eval do
|
50
|
+
define_method k do
|
51
|
+
@__cache[k] ||= v.call
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
ret = eval __hook, __binding, __filename
|
56
|
+
BufferManager.clear @__say_id if @__say_id
|
57
|
+
@__cache = {}
|
58
|
+
ret
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Singleton
|
63
|
+
|
64
|
+
def initialize dir
|
65
|
+
@dir = dir
|
66
|
+
@hooks = {}
|
67
|
+
@descs = {}
|
68
|
+
@contexts = {}
|
69
|
+
@tags = {}
|
70
|
+
|
71
|
+
Dir.mkdir dir unless File.exists? dir
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_reader :tags
|
75
|
+
|
76
|
+
def run name, locals={}
|
77
|
+
hook = hook_for(name) or return
|
78
|
+
context = @contexts[hook] ||= HookContext.new(name)
|
79
|
+
|
80
|
+
result = nil
|
81
|
+
begin
|
82
|
+
result = context.__run hook, fn_for(name), locals
|
83
|
+
rescue Exception => e
|
84
|
+
log "error running hook: #{e.message}"
|
85
|
+
log e.backtrace.join("\n")
|
86
|
+
@hooks[name] = nil # disable it
|
87
|
+
BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
|
88
|
+
end
|
89
|
+
result
|
90
|
+
end
|
91
|
+
|
92
|
+
def register name, desc
|
93
|
+
@descs[name] = desc
|
94
|
+
end
|
95
|
+
|
96
|
+
def print_hooks f=$stdout
|
97
|
+
puts <<EOS
|
98
|
+
Have #{@descs.size} registered hooks:
|
99
|
+
|
100
|
+
EOS
|
101
|
+
|
102
|
+
@descs.sort.each do |name, desc|
|
103
|
+
f.puts <<EOS
|
104
|
+
#{name}
|
105
|
+
#{"-" * name.length}
|
106
|
+
File: #{fn_for name}
|
107
|
+
#{desc}
|
108
|
+
EOS
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def enabled? name; !hook_for(name).nil? end
|
113
|
+
|
114
|
+
def clear; @hooks.clear; end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def hook_for name
|
119
|
+
unless @hooks.member? name
|
120
|
+
@hooks[name] = begin
|
121
|
+
returning IO.read(fn_for(name)) do
|
122
|
+
debug "read '#{name}' from #{fn_for(name)}"
|
123
|
+
end
|
124
|
+
rescue SystemCallError => e
|
125
|
+
#debug "disabled hook for '#{name}': #{e.message}"
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
@hooks[name]
|
131
|
+
end
|
132
|
+
|
133
|
+
def fn_for name
|
134
|
+
File.join @dir, "#{name}.rb"
|
135
|
+
end
|
136
|
+
|
137
|
+
def log m
|
138
|
+
info("hook: " + m)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class HookManager
|
4
|
+
class HookContext
|
5
|
+
def initialize name
|
6
|
+
@__say_id = nil
|
7
|
+
@__name = name
|
8
|
+
@__locals = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_writer :__locals
|
12
|
+
def method_missing m, *a
|
13
|
+
case @__locals[m]
|
14
|
+
when Proc
|
15
|
+
@__locals[m] = @__locals[m].call(*a) # only call the proc once
|
16
|
+
when nil
|
17
|
+
super
|
18
|
+
else
|
19
|
+
@__locals[m]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def say s
|
24
|
+
if BufferManager.instantiated?
|
25
|
+
@__say_id = BufferManager.say s, @__say_id
|
26
|
+
BufferManager.draw_screen
|
27
|
+
else
|
28
|
+
log s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def log s
|
33
|
+
Redwood::log "hook[#@__name]: #{s}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def ask_yes_or_no q
|
37
|
+
if BufferManager.instantiated?
|
38
|
+
BufferManager.ask_yes_or_no q
|
39
|
+
else
|
40
|
+
print q
|
41
|
+
gets.chomp.downcase == 'y'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get tag
|
46
|
+
HookManager.tags[tag]
|
47
|
+
end
|
48
|
+
|
49
|
+
def set tag, value
|
50
|
+
HookManager.tags[tag] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def __binding
|
54
|
+
binding
|
55
|
+
end
|
56
|
+
|
57
|
+
def __cleanup
|
58
|
+
BufferManager.clear @__say_id if @__say_id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Singleton
|
63
|
+
|
64
|
+
def initialize dir
|
65
|
+
@dir = dir
|
66
|
+
@hooks = {}
|
67
|
+
@descs = {}
|
68
|
+
@contexts = {}
|
69
|
+
@tags = {}
|
70
|
+
|
71
|
+
Dir.mkdir dir unless File.exists? dir
|
72
|
+
|
73
|
+
self.class.i_am_the_instance self
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :tags
|
77
|
+
|
78
|
+
def run name, locals={}
|
79
|
+
hook = hook_for(name) or return
|
80
|
+
context = @contexts[hook] ||= HookContext.new(name)
|
81
|
+
context.__locals = locals
|
82
|
+
|
83
|
+
result = nil
|
84
|
+
begin
|
85
|
+
result = context.instance_eval @hooks[name], fn_for(name)
|
86
|
+
rescue Exception => e
|
87
|
+
log "error running hook: #{e.message}"
|
88
|
+
log e.backtrace.join("\n")
|
89
|
+
@hooks[name] = nil # disable it
|
90
|
+
BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
|
91
|
+
end
|
92
|
+
context.__cleanup
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
def register name, desc
|
97
|
+
@descs[name] = desc
|
98
|
+
end
|
99
|
+
|
100
|
+
def print_hooks f=$stdout
|
101
|
+
puts <<EOS
|
102
|
+
Have #{@descs.size} registered hooks:
|
103
|
+
|
104
|
+
EOS
|
105
|
+
|
106
|
+
@descs.sort.each do |name, desc|
|
107
|
+
f.puts <<EOS
|
108
|
+
#{name}
|
109
|
+
#{"-" * name.length}
|
110
|
+
File: #{fn_for name}
|
111
|
+
#{desc}
|
112
|
+
EOS
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def enabled? name; !hook_for(name).nil? end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def hook_for name
|
121
|
+
unless @hooks.member? name
|
122
|
+
@hooks[name] =
|
123
|
+
begin
|
124
|
+
returning IO.read(fn_for(name)) do
|
125
|
+
log "read '#{name}' from #{fn_for(name)}"
|
126
|
+
end
|
127
|
+
rescue SystemCallError => e
|
128
|
+
#log "disabled hook for '#{name}': #{e.message}"
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@hooks[name]
|
134
|
+
end
|
135
|
+
|
136
|
+
def fn_for name
|
137
|
+
File.join @dir, "#{name}.rb"
|
138
|
+
end
|
139
|
+
|
140
|
+
def log m
|
141
|
+
Redwood::log("hook: " + m)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class HookManager
|
4
|
+
class HookContext
|
5
|
+
def initialize name
|
6
|
+
@__say_id = nil
|
7
|
+
@__name = name
|
8
|
+
@__locals = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_writer :__locals
|
12
|
+
def method_missing m, *a
|
13
|
+
case @__locals[m]
|
14
|
+
when Proc
|
15
|
+
@__locals[m] = @__locals[m].call(*a) # only call the proc once
|
16
|
+
when nil
|
17
|
+
super
|
18
|
+
else
|
19
|
+
@__locals[m]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def say s
|
24
|
+
if BufferManager.instantiated?
|
25
|
+
@__say_id = BufferManager.say s, @__say_id
|
26
|
+
BufferManager.draw_screen
|
27
|
+
else
|
28
|
+
log s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def log s
|
33
|
+
Redwood::log "hook[#@__name]: #{s}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def ask_yes_or_no q
|
37
|
+
if BufferManager.instantiated?
|
38
|
+
BufferManager.ask_yes_or_no q
|
39
|
+
else
|
40
|
+
print q
|
41
|
+
gets.chomp.downcase == 'y'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get tag
|
46
|
+
HookManager.tags[tag]
|
47
|
+
end
|
48
|
+
|
49
|
+
def set tag, value
|
50
|
+
HookManager.tags[tag] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
def __binding
|
54
|
+
binding
|
55
|
+
end
|
56
|
+
|
57
|
+
def __cleanup
|
58
|
+
BufferManager.clear @__say_id if @__say_id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
include Singleton
|
63
|
+
|
64
|
+
def initialize dir
|
65
|
+
@dir = dir
|
66
|
+
@hooks = {}
|
67
|
+
@descs = {}
|
68
|
+
@contexts = {}
|
69
|
+
@tags = {}
|
70
|
+
|
71
|
+
Dir.mkdir dir unless File.exists? dir
|
72
|
+
|
73
|
+
self.class.i_am_the_instance self
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :tags
|
77
|
+
|
78
|
+
def run name, locals={}
|
79
|
+
hook = hook_for(name) or return
|
80
|
+
context = @contexts[hook] ||= HookContext.new(name)
|
81
|
+
context.__locals = locals
|
82
|
+
|
83
|
+
result = nil
|
84
|
+
begin
|
85
|
+
result = context.instance_eval @hooks[name], fn_for(name)
|
86
|
+
rescue Exception => e
|
87
|
+
log "error running hook: #{e.message}"
|
88
|
+
log e.backtrace.join("\n")
|
89
|
+
@hooks[name] = nil # disable it
|
90
|
+
BufferManager.flash "Error running hook: #{e.message}" if BufferManager.instantiated?
|
91
|
+
end
|
92
|
+
context.__cleanup
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
def register name, desc
|
97
|
+
@descs[name] = desc
|
98
|
+
end
|
99
|
+
|
100
|
+
def print_hooks f=$stdout
|
101
|
+
puts <<EOS
|
102
|
+
Have #{@descs.size} registered hooks:
|
103
|
+
|
104
|
+
EOS
|
105
|
+
|
106
|
+
@descs.sort.each do |name, desc|
|
107
|
+
f.puts <<EOS
|
108
|
+
#{name}
|
109
|
+
#{"-" * name.length}
|
110
|
+
File: #{fn_for name}
|
111
|
+
#{desc}
|
112
|
+
EOS
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def enabled? name; !hook_for(name).nil? end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def hook_for name
|
121
|
+
unless @hooks.member? name
|
122
|
+
@hooks[name] =
|
123
|
+
begin
|
124
|
+
returning IO.read(fn_for(name)) do
|
125
|
+
log "read '#{name}' from #{fn_for(name)}"
|
126
|
+
end
|
127
|
+
rescue SystemCallError => e
|
128
|
+
#log "disabled hook for '#{name}': #{e.message}"
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
@hooks[name]
|
134
|
+
end
|
135
|
+
|
136
|
+
def fn_for name
|
137
|
+
File.join @dir, "#{name}.rb"
|
138
|
+
end
|
139
|
+
|
140
|
+
def log m
|
141
|
+
Redwood::log("hook: " + m)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/sup/imap.rb
CHANGED
@@ -4,6 +4,7 @@ require 'stringio'
|
|
4
4
|
require 'time'
|
5
5
|
require 'rmail'
|
6
6
|
require 'cgi'
|
7
|
+
require 'set'
|
7
8
|
|
8
9
|
## TODO: remove synchronized method protector calls; use a Monitor instead
|
9
10
|
## (ruby's reentrant mutex)
|
@@ -47,6 +48,7 @@ require 'cgi'
|
|
47
48
|
module Redwood
|
48
49
|
|
49
50
|
class IMAP < Source
|
51
|
+
include SerializeLabelsNicely
|
50
52
|
SCAN_INTERVAL = 60 # seconds
|
51
53
|
|
52
54
|
## upon these errors we'll try to rereconnect a few times
|
@@ -69,7 +71,7 @@ class IMAP < Source
|
|
69
71
|
@imap_state = {}
|
70
72
|
@ids = []
|
71
73
|
@last_scan = nil
|
72
|
-
@labels = ((labels || []) - LabelManager::RESERVED_LABELS)
|
74
|
+
@labels = Set.new((labels || []) - LabelManager::RESERVED_LABELS)
|
73
75
|
@say_id = nil
|
74
76
|
@mutex = Mutex.new
|
75
77
|
end
|
@@ -111,6 +113,14 @@ class IMAP < Source
|
|
111
113
|
end
|
112
114
|
synchronized :raw_header
|
113
115
|
|
116
|
+
def store_message date, from_email, &block
|
117
|
+
message = StringIO.new
|
118
|
+
yield message
|
119
|
+
message.string.gsub! /\n/, "\r\n"
|
120
|
+
|
121
|
+
safely { @imap.append mailbox, message.string, [:Seen], Time.now }
|
122
|
+
end
|
123
|
+
|
114
124
|
def raw_message id
|
115
125
|
unsynchronized_scan_mailbox
|
116
126
|
get_imap_fields(id, 'RFC822').first.gsub(/\r\n/, "\n")
|
@@ -151,13 +161,13 @@ class IMAP < Source
|
|
151
161
|
return if last_id == @ids.length
|
152
162
|
|
153
163
|
range = (@ids.length + 1) .. last_id
|
154
|
-
|
164
|
+
debug "fetching IMAP headers #{range}"
|
155
165
|
fetch(range, ['RFC822.SIZE', 'INTERNALDATE', 'FLAGS']).each do |v|
|
156
166
|
id = make_id v
|
157
167
|
@ids << id
|
158
168
|
@imap_state[id] = { :id => v.seqno, :flags => v.attr["FLAGS"] }
|
159
169
|
end
|
160
|
-
|
170
|
+
debug "done fetching IMAP headers"
|
161
171
|
end
|
162
172
|
synchronized :scan_mailbox
|
163
173
|
|
@@ -216,7 +226,7 @@ private
|
|
216
226
|
if good_results.empty?
|
217
227
|
raise FatalSourceError, "no IMAP response for #{ids} containing all fields #{fields.join(', ')} (got #{results.size} results)"
|
218
228
|
elsif good_results.size < results.size
|
219
|
-
|
229
|
+
warn "Your IMAP server sucks. It sent #{results.size} results for a request for #{good_results.size} messages. What are you using, Binc?"
|
220
230
|
end
|
221
231
|
|
222
232
|
good_results
|
@@ -244,12 +254,12 @@ private
|
|
244
254
|
raise Net::IMAP::NoResponseError unless @imap.capability().member? "AUTH=CRAM-MD5"
|
245
255
|
@imap.authenticate 'CRAM-MD5', @username, @password
|
246
256
|
rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
|
247
|
-
|
257
|
+
debug "CRAM-MD5 authentication failed: #{e.class}. Trying LOGIN auth..."
|
248
258
|
begin
|
249
259
|
raise Net::IMAP::NoResponseError unless @imap.capability().member? "AUTH=LOGIN"
|
250
260
|
@imap.authenticate 'LOGIN', @username, @password
|
251
261
|
rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError => e
|
252
|
-
|
262
|
+
debug "LOGIN authentication failed: #{e.class}. Trying plain-text LOGIN..."
|
253
263
|
@imap.login @username, @password
|
254
264
|
end
|
255
265
|
end
|
@@ -266,7 +276,7 @@ private
|
|
266
276
|
|
267
277
|
def say s
|
268
278
|
@say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
|
269
|
-
|
279
|
+
info s
|
270
280
|
end
|
271
281
|
|
272
282
|
def shutup
|
@@ -323,7 +333,7 @@ private
|
|
323
333
|
rescue *RECOVERABLE_ERRORS => e
|
324
334
|
if (retries += 1) <= 3
|
325
335
|
@imap = nil
|
326
|
-
|
336
|
+
warn "got #{e.class.name}: #{e.message.inspect}"
|
327
337
|
sleep 2
|
328
338
|
retry
|
329
339
|
end
|