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
@@ -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
@@ -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).uniq.freeze
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
- Redwood::log "fetching IMAP headers #{range}"
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
- Redwood::log "done fetching IMAP headers"
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
- Redwood::log "Your IMAP server sucks. It sent #{results.size} results for a request for #{good_results.size} messages. What are you using, Binc?"
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
- Redwood::log "CRAM-MD5 authentication failed: #{e.class}. Trying LOGIN auth..."
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
- Redwood::log "LOGIN authentication failed: #{e.class}. Trying plain-text LOGIN..."
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
- Redwood::log s
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
- Redwood::log "got #{e.class.name}: #{e.message.inspect}"
336
+ warn "got #{e.class.name}: #{e.message.inspect}"
327
337
  sleep 2
328
338
  retry
329
339
  end