sup 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|