sup 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTORS +84 -0
- data/Gemfile +3 -0
- data/HACKING +42 -0
- data/History.txt +361 -0
- data/LICENSE +280 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/ReleaseNotes +231 -0
- data/bin/sup +434 -0
- data/bin/sup-add +118 -0
- data/bin/sup-config +243 -0
- data/bin/sup-dump +43 -0
- data/bin/sup-import-dump +101 -0
- data/bin/sup-psych-ify-config-files +21 -0
- data/bin/sup-recover-sources +87 -0
- data/bin/sup-sync +210 -0
- data/bin/sup-sync-back-maildir +127 -0
- data/bin/sup-tweak-labels +140 -0
- data/contrib/colorpicker.rb +100 -0
- data/contrib/completion/_sup.zsh +114 -0
- data/devel/console.sh +3 -0
- data/devel/count-loc.sh +3 -0
- data/devel/load-index.rb +9 -0
- data/devel/profile.rb +12 -0
- data/devel/start-console.rb +5 -0
- data/doc/FAQ.txt +119 -0
- data/doc/Hooks.txt +79 -0
- data/doc/Philosophy.txt +69 -0
- data/lib/sup.rb +467 -0
- data/lib/sup/account.rb +90 -0
- data/lib/sup/buffer.rb +768 -0
- data/lib/sup/colormap.rb +239 -0
- data/lib/sup/contact.rb +67 -0
- data/lib/sup/crypto.rb +461 -0
- data/lib/sup/draft.rb +119 -0
- data/lib/sup/hook.rb +159 -0
- data/lib/sup/horizontal_selector.rb +59 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +882 -0
- data/lib/sup/interactive_lock.rb +89 -0
- data/lib/sup/keymap.rb +140 -0
- data/lib/sup/label.rb +87 -0
- data/lib/sup/logger.rb +77 -0
- data/lib/sup/logger/singleton.rb +10 -0
- data/lib/sup/maildir.rb +257 -0
- data/lib/sup/mbox.rb +187 -0
- data/lib/sup/message.rb +803 -0
- data/lib/sup/message_chunks.rb +328 -0
- data/lib/sup/mode.rb +140 -0
- data/lib/sup/modes/buffer_list_mode.rb +50 -0
- data/lib/sup/modes/completion_mode.rb +55 -0
- data/lib/sup/modes/compose_mode.rb +38 -0
- data/lib/sup/modes/console_mode.rb +125 -0
- data/lib/sup/modes/contact_list_mode.rb +148 -0
- data/lib/sup/modes/edit_message_async_mode.rb +110 -0
- data/lib/sup/modes/edit_message_mode.rb +728 -0
- data/lib/sup/modes/file_browser_mode.rb +109 -0
- data/lib/sup/modes/forward_mode.rb +82 -0
- data/lib/sup/modes/help_mode.rb +19 -0
- data/lib/sup/modes/inbox_mode.rb +85 -0
- data/lib/sup/modes/label_list_mode.rb +138 -0
- data/lib/sup/modes/label_search_results_mode.rb +38 -0
- data/lib/sup/modes/line_cursor_mode.rb +203 -0
- data/lib/sup/modes/log_mode.rb +57 -0
- data/lib/sup/modes/person_search_results_mode.rb +12 -0
- data/lib/sup/modes/poll_mode.rb +19 -0
- data/lib/sup/modes/reply_mode.rb +228 -0
- data/lib/sup/modes/resume_mode.rb +52 -0
- data/lib/sup/modes/scroll_mode.rb +252 -0
- data/lib/sup/modes/search_list_mode.rb +204 -0
- data/lib/sup/modes/search_results_mode.rb +59 -0
- data/lib/sup/modes/text_mode.rb +76 -0
- data/lib/sup/modes/thread_index_mode.rb +1033 -0
- data/lib/sup/modes/thread_view_mode.rb +941 -0
- data/lib/sup/person.rb +134 -0
- data/lib/sup/poll.rb +272 -0
- data/lib/sup/rfc2047.rb +56 -0
- data/lib/sup/search.rb +110 -0
- data/lib/sup/sent.rb +58 -0
- data/lib/sup/service/label_service.rb +45 -0
- data/lib/sup/source.rb +244 -0
- data/lib/sup/tagger.rb +50 -0
- data/lib/sup/textfield.rb +253 -0
- data/lib/sup/thread.rb +452 -0
- data/lib/sup/time.rb +93 -0
- data/lib/sup/undo.rb +38 -0
- data/lib/sup/update.rb +30 -0
- data/lib/sup/util.rb +747 -0
- data/lib/sup/util/ncurses.rb +274 -0
- data/lib/sup/util/path.rb +9 -0
- data/lib/sup/util/query.rb +17 -0
- data/lib/sup/util/uri.rb +15 -0
- data/lib/sup/version.rb +3 -0
- data/sup.gemspec +53 -0
- data/test/dummy_source.rb +61 -0
- data/test/gnupg_test_home/gpg.conf +1 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
- data/test/integration/test_label_service.rb +18 -0
- data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
- data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
- data/test/messages/missing-line.eml +9 -0
- data/test/test_crypto.rb +109 -0
- data/test/test_header_parsing.rb +168 -0
- data/test/test_helper.rb +7 -0
- data/test/test_message.rb +532 -0
- data/test/test_messages_dir.rb +147 -0
- data/test/test_yaml_migration.rb +85 -0
- data/test/test_yaml_regressions.rb +17 -0
- data/test/unit/service/test_label_service.rb +19 -0
- data/test/unit/test_horizontal_selector.rb +40 -0
- data/test/unit/util/test_query.rb +46 -0
- data/test/unit/util/test_string.rb +57 -0
- data/test/unit/util/test_uri.rb +19 -0
- metadata +423 -0
data/lib/sup/person.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class Person
|
4
|
+
attr_accessor :name, :email
|
5
|
+
|
6
|
+
def initialize name, email
|
7
|
+
raise ArgumentError, "email can't be nil" unless email
|
8
|
+
|
9
|
+
email.fix_encoding!
|
10
|
+
|
11
|
+
@name = if name
|
12
|
+
name.fix_encoding!
|
13
|
+
name = name.strip.gsub(/\s+/, " ")
|
14
|
+
name =~ /^(['"]\s*)(.*?)(\s*["'])$/ ? $2 : name
|
15
|
+
name.gsub('\\\\', '\\')
|
16
|
+
end
|
17
|
+
|
18
|
+
@email = email.strip.gsub(/\s+/, " ")
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s; "#@name <#@email>" end
|
22
|
+
|
23
|
+
# def == o; o && o.email == email; end
|
24
|
+
# alias :eql? :==
|
25
|
+
# def hash; [name, email].hash; end
|
26
|
+
|
27
|
+
def shortname
|
28
|
+
case @name
|
29
|
+
when /\S+, (\S+)/
|
30
|
+
$1
|
31
|
+
when /(\S+) \S+/
|
32
|
+
$1
|
33
|
+
when nil
|
34
|
+
@email
|
35
|
+
else
|
36
|
+
@name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def longname
|
41
|
+
if @name && @email
|
42
|
+
"#@name <#@email>"
|
43
|
+
else
|
44
|
+
@email
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def mediumname; @name || @email; end
|
49
|
+
|
50
|
+
def Person.full_address name, email
|
51
|
+
if name && email
|
52
|
+
if name =~ /[",@]/
|
53
|
+
"#{name.inspect} <#{email}>" # escape quotes
|
54
|
+
else
|
55
|
+
"#{name} <#{email}>"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
email
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def full_address
|
63
|
+
Person.full_address @name, @email
|
64
|
+
end
|
65
|
+
|
66
|
+
## when sorting addresses, sort by this
|
67
|
+
def sort_by_me
|
68
|
+
case @name
|
69
|
+
when /^(\S+), \S+/
|
70
|
+
$1
|
71
|
+
when /^\S+ \S+ (\S+)/
|
72
|
+
$1
|
73
|
+
when /^\S+ (\S+)/
|
74
|
+
$1
|
75
|
+
when nil
|
76
|
+
@email
|
77
|
+
else
|
78
|
+
@name
|
79
|
+
end.downcase
|
80
|
+
end
|
81
|
+
|
82
|
+
## return "canonical" person using contact manager or create one if
|
83
|
+
## not found or contact manager not available
|
84
|
+
def self.from_name_and_email name, email
|
85
|
+
ContactManager.instantiated? && ContactManager.person_for(email) || Person.new(name, email)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.from_address s
|
89
|
+
return nil if s.nil?
|
90
|
+
|
91
|
+
## try and parse an email address and name
|
92
|
+
name, email = case s
|
93
|
+
when /(.+?) ((\S+?)@\S+) \3/
|
94
|
+
## ok, this first match cause is insane, but bear with me. email
|
95
|
+
## addresses are stored in the to/from/etc fields of the index in a
|
96
|
+
## weird format: "name address first-part-of-address", i.e. spaces
|
97
|
+
## separating those three bits, and no <>'s. this is the output of
|
98
|
+
## #indexable_content. here, we reverse-engineer that format to extract
|
99
|
+
## a valid address.
|
100
|
+
##
|
101
|
+
## we store things this way to allow searches on a to/from/etc field to
|
102
|
+
## match any of those parts. a more robust solution would be to store a
|
103
|
+
## separate, non-indexed field with the proper headers. but this way we
|
104
|
+
## save precious bits, and it's backwards-compatible with older indexes.
|
105
|
+
[$1, $2]
|
106
|
+
when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
|
107
|
+
a, b = $1, $2
|
108
|
+
[a.gsub('\"', '"'), b]
|
109
|
+
when /<((\S+?)@\S+?)>/
|
110
|
+
[$2, $1]
|
111
|
+
when /((\S+?)@\S+)/
|
112
|
+
[$2, $1]
|
113
|
+
else
|
114
|
+
[nil, s]
|
115
|
+
end
|
116
|
+
|
117
|
+
from_name_and_email name, email
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.from_address_list ss
|
121
|
+
return [] if ss.nil?
|
122
|
+
ss.dup.split_on_commas.map { |s| self.from_address s }
|
123
|
+
end
|
124
|
+
|
125
|
+
## see comments in self.from_address
|
126
|
+
def indexable_content
|
127
|
+
[name, email, email.split(/@/).first].join(" ")
|
128
|
+
end
|
129
|
+
|
130
|
+
def eql? o; email.eql? o.email end
|
131
|
+
def hash; email.hash end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
data/lib/sup/poll.rb
ADDED
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Redwood
|
4
|
+
|
5
|
+
class PollManager
|
6
|
+
include Redwood::Singleton
|
7
|
+
|
8
|
+
HookManager.register "before-add-message", <<EOS
|
9
|
+
Executes immediately before a message is added to the index.
|
10
|
+
Variables:
|
11
|
+
message: the new message
|
12
|
+
EOS
|
13
|
+
|
14
|
+
HookManager.register "before-poll", <<EOS
|
15
|
+
Executes immediately before a poll for new messages commences.
|
16
|
+
No variables.
|
17
|
+
EOS
|
18
|
+
|
19
|
+
HookManager.register "after-poll", <<EOS
|
20
|
+
Executes immediately after a poll for new messages completes.
|
21
|
+
Variables:
|
22
|
+
num: the total number of new messages added in this poll
|
23
|
+
num_inbox: the number of new messages added in this poll which
|
24
|
+
appear in the inbox (i.e. were not auto-archived).
|
25
|
+
num_total: the total number of messages
|
26
|
+
num_inbox_total: the total number of new messages in the inbox.
|
27
|
+
num_inbox_total_unread: the total number of unread messages in the inbox
|
28
|
+
num_updated: the total number of updated messages
|
29
|
+
num_deleted: the total number of deleted messages
|
30
|
+
labels: the labels that were applied
|
31
|
+
from_and_subj: an array of (from email address, subject) pairs
|
32
|
+
from_and_subj_inbox: an array of (from email address, subject) pairs for
|
33
|
+
only those messages appearing in the inbox
|
34
|
+
EOS
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@delay = $config[:poll_interval] || 300
|
38
|
+
@mutex = Mutex.new
|
39
|
+
@thread = nil
|
40
|
+
@last_poll = nil
|
41
|
+
@polling = Mutex.new
|
42
|
+
@poll_sources = nil
|
43
|
+
@mode = nil
|
44
|
+
@should_clear_running_totals = false
|
45
|
+
clear_running_totals # defines @running_totals
|
46
|
+
UpdateManager.register self
|
47
|
+
end
|
48
|
+
|
49
|
+
def poll_with_sources
|
50
|
+
@mode ||= PollMode.new
|
51
|
+
|
52
|
+
if HookManager.enabled? "before-poll"
|
53
|
+
HookManager.run("before-poll")
|
54
|
+
else
|
55
|
+
BufferManager.flash "Polling for new messages..."
|
56
|
+
end
|
57
|
+
|
58
|
+
num, numi, numu, numd, from_and_subj, from_and_subj_inbox, loaded_labels = @mode.poll
|
59
|
+
clear_running_totals if @should_clear_running_totals
|
60
|
+
@running_totals[:num] += num
|
61
|
+
@running_totals[:numi] += numi
|
62
|
+
@running_totals[:numu] += numu
|
63
|
+
@running_totals[:numd] += numd
|
64
|
+
@running_totals[:loaded_labels] += loaded_labels || []
|
65
|
+
|
66
|
+
|
67
|
+
if HookManager.enabled? "after-poll"
|
68
|
+
hook_args = { :num => num, :num_inbox => numi,
|
69
|
+
:num_total => @running_totals[:num], :num_inbox_total => @running_totals[:numi],
|
70
|
+
:num_updated => @running_totals[:numu],
|
71
|
+
:num_deleted => @running_totals[:numd],
|
72
|
+
:labels => @running_totals[:loaded_labels],
|
73
|
+
:from_and_subj => from_and_subj, :from_and_subj_inbox => from_and_subj_inbox,
|
74
|
+
:num_inbox_total_unread => lambda { Index.num_results_for :labels => [:inbox, :unread] } }
|
75
|
+
|
76
|
+
HookManager.run("after-poll", hook_args)
|
77
|
+
else
|
78
|
+
if @running_totals[:num] > 0
|
79
|
+
flash_msg = "Loaded #{@running_totals[:num].pluralize 'new message'}, #{@running_totals[:numi]} to inbox. " if @running_totals[:num] > 0
|
80
|
+
flash_msg += "Updated #{@running_totals[:numu].pluralize 'message'}. " if @running_totals[:numu] > 0
|
81
|
+
flash_msg += "Deleted #{@running_totals[:numd].pluralize 'message'}. " if @running_totals[:numd] > 0
|
82
|
+
flash_msg += "Labels: #{@running_totals[:loaded_labels].map{|l| l.to_s}.join(', ')}." if @running_totals[:loaded_labels].size > 0
|
83
|
+
BufferManager.flash flash_msg
|
84
|
+
else
|
85
|
+
BufferManager.flash "No new messages."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
def poll
|
92
|
+
if @polling.try_lock
|
93
|
+
@poll_sources = SourceManager.usual_sources
|
94
|
+
num, numi = poll_with_sources
|
95
|
+
@polling.unlock
|
96
|
+
[num, numi]
|
97
|
+
else
|
98
|
+
debug "poll already in progress."
|
99
|
+
return
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def poll_unusual
|
104
|
+
if @polling.try_lock
|
105
|
+
@poll_sources = SourceManager.unusual_sources
|
106
|
+
num, numi = poll_with_sources
|
107
|
+
@polling.unlock
|
108
|
+
[num, numi]
|
109
|
+
else
|
110
|
+
debug "poll_unusual already in progress."
|
111
|
+
return
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def start
|
116
|
+
@thread = Redwood::reporting_thread("periodic poll") do
|
117
|
+
while true
|
118
|
+
sleep @delay / 2
|
119
|
+
poll if @last_poll.nil? || (Time.now - @last_poll) >= @delay
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def stop
|
125
|
+
@thread.kill if @thread
|
126
|
+
@thread = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def do_poll
|
130
|
+
total_num = total_numi = total_numu = total_numd = 0
|
131
|
+
from_and_subj = []
|
132
|
+
from_and_subj_inbox = []
|
133
|
+
loaded_labels = Set.new
|
134
|
+
|
135
|
+
@mutex.synchronize do
|
136
|
+
@poll_sources.each do |source|
|
137
|
+
begin
|
138
|
+
yield "Loading from #{source}... "
|
139
|
+
rescue SourceError => e
|
140
|
+
warn "problem getting messages from #{source}: #{e.message}"
|
141
|
+
next
|
142
|
+
end
|
143
|
+
|
144
|
+
msg = ""
|
145
|
+
num = numi = numu = numd = 0
|
146
|
+
poll_from source do |action,m,old_m,progress|
|
147
|
+
if action == :delete
|
148
|
+
yield "Deleting #{m.id}"
|
149
|
+
loaded_labels.merge m.labels
|
150
|
+
numd += 1
|
151
|
+
elsif action == :update
|
152
|
+
yield "Message at #{m.source_info} is an update of an old message. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
|
153
|
+
loaded_labels.merge m.labels
|
154
|
+
numu += 1
|
155
|
+
elsif action == :add
|
156
|
+
if old_m
|
157
|
+
new_locations = (m.locations - old_m.locations)
|
158
|
+
if not new_locations.empty?
|
159
|
+
yield "Message at #{new_locations[0].info} has changed its source location. Updating labels from #{old_m.labels.to_a * ','} => #{m.labels.to_a * ','}"
|
160
|
+
numu += 1
|
161
|
+
else
|
162
|
+
yield "Skipping already-imported message at #{m.locations[-1].info}"
|
163
|
+
end
|
164
|
+
else
|
165
|
+
yield "Found new message at #{m.source_info} with labels #{m.labels.to_a * ','}"
|
166
|
+
loaded_labels.merge m.labels
|
167
|
+
num += 1
|
168
|
+
from_and_subj << [m.from && m.from.longname, m.subj]
|
169
|
+
if (m.labels & [:inbox, :spam, :deleted, :killed]) == Set.new([:inbox])
|
170
|
+
from_and_subj_inbox << [m.from && m.from.longname, m.subj]
|
171
|
+
numi += 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
else fail
|
175
|
+
end
|
176
|
+
end
|
177
|
+
msg += "Found #{num} messages, #{numi} to inbox. " unless num == 0
|
178
|
+
msg += "Updated #{numu} messages. " unless numu == 0
|
179
|
+
msg += "Deleted #{numd} messages." unless numd == 0
|
180
|
+
yield msg unless msg == ""
|
181
|
+
total_num += num
|
182
|
+
total_numi += numi
|
183
|
+
total_numu += numu
|
184
|
+
total_numd += numd
|
185
|
+
end
|
186
|
+
|
187
|
+
loaded_labels = loaded_labels - LabelManager::HIDDEN_RESERVED_LABELS - [:inbox, :killed]
|
188
|
+
yield "Done polling; loaded #{total_num} new messages total"
|
189
|
+
@last_poll = Time.now
|
190
|
+
end
|
191
|
+
[total_num, total_numi, total_numu, total_numd, from_and_subj, from_and_subj_inbox, loaded_labels]
|
192
|
+
end
|
193
|
+
|
194
|
+
## like Source#poll, but yields successive Message objects, which have their
|
195
|
+
## labels and locations set correctly. The Messages are saved to or removed
|
196
|
+
## from the index after being yielded.
|
197
|
+
def poll_from source, opts={}
|
198
|
+
debug "trying to acquire poll lock for: #{source}..."
|
199
|
+
if source.try_lock
|
200
|
+
begin
|
201
|
+
source.poll do |sym, args|
|
202
|
+
case sym
|
203
|
+
when :add
|
204
|
+
m = Message.build_from_source source, args[:info]
|
205
|
+
old_m = Index.build_message m.id
|
206
|
+
m.labels += args[:labels]
|
207
|
+
m.labels.delete :inbox if source.archived?
|
208
|
+
m.labels.delete :unread if source.read?
|
209
|
+
m.labels.delete :unread if m.source_marked_read? # preserve read status if possible
|
210
|
+
m.labels.each { |l| LabelManager << l }
|
211
|
+
m.labels = old_m.labels + (m.labels - [:unread, :inbox]) if old_m
|
212
|
+
m.locations = old_m.locations + m.locations if old_m
|
213
|
+
HookManager.run "before-add-message", :message => m
|
214
|
+
yield :add, m, old_m, args[:progress] if block_given?
|
215
|
+
Index.sync_message m, true
|
216
|
+
|
217
|
+
if Index.message_joining_killed? m
|
218
|
+
m.labels += [:killed]
|
219
|
+
Index.sync_message m, true
|
220
|
+
end
|
221
|
+
|
222
|
+
## We need to add or unhide the message when it either did not exist
|
223
|
+
## before at all or when it was updated. We do *not* add/unhide when
|
224
|
+
## the same message was found at a different location
|
225
|
+
if old_m
|
226
|
+
UpdateManager.relay self, :updated, m
|
227
|
+
elsif !old_m or not old_m.locations.member? m.location
|
228
|
+
UpdateManager.relay self, :added, m
|
229
|
+
end
|
230
|
+
when :delete
|
231
|
+
Index.each_message({:location => [source.id, args[:info]]}, false) do |m|
|
232
|
+
m.locations.delete Location.new(source, args[:info])
|
233
|
+
Index.sync_message m, false
|
234
|
+
if m.locations.size == 0
|
235
|
+
yield :delete, m, [source,args[:info]], args[:progress] if block_given?
|
236
|
+
Index.delete m.id
|
237
|
+
UpdateManager.relay self, :location_deleted, m
|
238
|
+
end
|
239
|
+
end
|
240
|
+
when :update
|
241
|
+
Index.each_message({:location => [source.id, args[:old_info]]}, false) do |m|
|
242
|
+
old_m = Index.build_message m.id
|
243
|
+
m.locations.delete Location.new(source, args[:old_info])
|
244
|
+
m.locations.push Location.new(source, args[:new_info])
|
245
|
+
## Update labels that might have been modified remotely
|
246
|
+
m.labels -= source.supported_labels?
|
247
|
+
m.labels += args[:labels]
|
248
|
+
yield :update, m, old_m if block_given?
|
249
|
+
Index.sync_message m, true
|
250
|
+
UpdateManager.relay self, :updated, m
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
rescue SourceError => e
|
256
|
+
warn "problem getting messages from #{source}: #{e.message}"
|
257
|
+
|
258
|
+
ensure
|
259
|
+
source.go_idle
|
260
|
+
source.unlock
|
261
|
+
end
|
262
|
+
else
|
263
|
+
debug "source #{source} is already being polled."
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def handle_idle_update sender, idle_since; @should_clear_running_totals = false; end
|
268
|
+
def handle_unidle_update sender, idle_since; @should_clear_running_totals = true; clear_running_totals; end
|
269
|
+
def clear_running_totals; @running_totals = {:num => 0, :numi => 0, :numu => 0, :numd => 0, :loaded_labels => Set.new}; end
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
data/lib/sup/rfc2047.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
## from: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/101949
|
2
|
+
|
3
|
+
# $Id: rfc2047.rb,v 1.4 2003/04/18 20:55:56 sam Exp $
|
4
|
+
# MODIFIED slightly by William Morgan
|
5
|
+
#
|
6
|
+
# An implementation of RFC 2047 decoding.
|
7
|
+
#
|
8
|
+
# This module depends on the iconv library by Nobuyoshi Nakada, which I've
|
9
|
+
# heard may be distributed as a standard part of Ruby 1.8. Many thanks to him
|
10
|
+
# for helping with building and using iconv.
|
11
|
+
#
|
12
|
+
# Thanks to "Josef 'Jupp' Schugt" <jupp / gmx.de> for pointing out an error with
|
13
|
+
# stateful character sets.
|
14
|
+
#
|
15
|
+
# Copyright (c) Sam Roberts <sroberts / uniserve.com> 2004
|
16
|
+
#
|
17
|
+
# This file is distributed under the same terms as Ruby.
|
18
|
+
|
19
|
+
module Rfc2047
|
20
|
+
WORD = %r{=\?([!\#$%&'*+-/0-9A-Z\\^\`a-z{|}~]+)\?([BbQq])\?([!->@-~]+)\?=} # :nodoc: 'stupid ruby-mode
|
21
|
+
WORDSEQ = %r{(#{WORD.source})\s+(?=#{WORD.source})}
|
22
|
+
|
23
|
+
def Rfc2047.is_encoded? s; s =~ WORD end
|
24
|
+
|
25
|
+
# Decodes a string, +from+, containing RFC 2047 encoded words into a target
|
26
|
+
# character set, +target+. See iconv_open(3) for information on the
|
27
|
+
# supported target encodings. If one of the encoded words cannot be
|
28
|
+
# converted to the target encoding, it is left in its encoded form.
|
29
|
+
def Rfc2047.decode_to(target, from)
|
30
|
+
from = from.gsub(WORDSEQ, '\1')
|
31
|
+
out = from.gsub(WORD) do
|
32
|
+
|word|
|
33
|
+
charset, encoding, text = $1, $2, $3
|
34
|
+
|
35
|
+
# B64 or QP decode, as necessary:
|
36
|
+
case encoding
|
37
|
+
when 'b', 'B'
|
38
|
+
#puts text
|
39
|
+
text = text.unpack('m*')[0]
|
40
|
+
#puts text.dump
|
41
|
+
|
42
|
+
when 'q', 'Q'
|
43
|
+
# RFC 2047 has a variant of quoted printable where a ' ' character
|
44
|
+
# can be represented as an '_', rather than =32, so convert
|
45
|
+
# any of these that we find before doing the QP decoding.
|
46
|
+
text = text.tr("_", " ")
|
47
|
+
text = text.unpack('M*')[0]
|
48
|
+
|
49
|
+
# Don't need an else, because no other values can be matched in a
|
50
|
+
# WORD.
|
51
|
+
end
|
52
|
+
|
53
|
+
text.transcode(target, charset)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|