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/bin/sup-sync
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'rubygems'
|
|
7
|
+
require 'trollop'
|
|
8
|
+
require "sup"
|
|
9
|
+
|
|
10
|
+
PROGRESS_UPDATE_INTERVAL = 15 # seconds
|
|
11
|
+
|
|
12
|
+
class Float
|
|
13
|
+
def to_s; sprintf '%.2f', self; end
|
|
14
|
+
def to_time_s; infinite? ? "unknown" : super end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Numeric
|
|
18
|
+
def to_time_s
|
|
19
|
+
i = to_i
|
|
20
|
+
sprintf "%d:%02d:%02d", i / 3600, (i / 60) % 60, i % 60
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Set
|
|
25
|
+
def to_s; to_a * ',' end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def time
|
|
29
|
+
startt = Time.now
|
|
30
|
+
yield
|
|
31
|
+
Time.now - startt
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opts = Trollop::options do
|
|
35
|
+
version "sup-sync (sup #{Redwood::VERSION})"
|
|
36
|
+
banner <<EOS
|
|
37
|
+
Synchronizes the Sup index with one or more message sources by adding
|
|
38
|
+
messages, deleting messages, or changing message state in the index as
|
|
39
|
+
appropriate.
|
|
40
|
+
|
|
41
|
+
"Message state" means read/unread, archived/inbox, starred/unstarred,
|
|
42
|
+
and all user-defined labels on each message.
|
|
43
|
+
|
|
44
|
+
"Default source state" refers to any state that a source itself has
|
|
45
|
+
keeps about a message. Sup-sync uses this information when adding a
|
|
46
|
+
new message to the index. The source state is typically limited to
|
|
47
|
+
read/unread, archived/inbox status and a single label based on the
|
|
48
|
+
source name. Messages using the default source state are placed in
|
|
49
|
+
the inbox (i.e. not archived) and unstarred.
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
sup-sync [options] <source>*
|
|
53
|
+
|
|
54
|
+
where <source>* is zero or more source URIs. If no sources are given,
|
|
55
|
+
sync from all usual sources. Supported source URI schemes can be seen
|
|
56
|
+
by running "sup-add --help".
|
|
57
|
+
|
|
58
|
+
Options controlling HOW message state is altered:
|
|
59
|
+
EOS
|
|
60
|
+
opt :asis, "If the message is already in the index, preserve its state. Otherwise, use default source state. (Default.)", :short => :none
|
|
61
|
+
opt :restore, "Restore message state from a dump file created with sup-dump. If a message is not in this dumpfile, act as --asis.", :type => String, :short => :none
|
|
62
|
+
opt :discard, "Discard any message state in the index and use the default source state. Dangerous!", :short => :none
|
|
63
|
+
opt :archive, "When using the default source state, mark messages as archived.", :short => "-x"
|
|
64
|
+
opt :read, "When using the default source state, mark messages as read."
|
|
65
|
+
opt :extra_labels, "When using the default source state, also apply these user-defined labels (a comma-separated list)", :default => "", :short => :none
|
|
66
|
+
|
|
67
|
+
text <<EOS
|
|
68
|
+
|
|
69
|
+
Other options:
|
|
70
|
+
EOS
|
|
71
|
+
opt :verbose, "Print message ids as they're processed."
|
|
72
|
+
opt :optimize, "As the final operation, optimize the index."
|
|
73
|
+
opt :all_sources, "Scan over all sources.", :short => :none
|
|
74
|
+
opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
|
|
75
|
+
opt :version, "Show version information", :short => :none
|
|
76
|
+
|
|
77
|
+
conflicts :asis, :restore, :discard
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
op = [:asis, :restore, :discard].find { |x| opts[x] } || :asis
|
|
81
|
+
|
|
82
|
+
Redwood::start
|
|
83
|
+
index = Redwood::Index.init
|
|
84
|
+
|
|
85
|
+
restored_state = if opts[:restore]
|
|
86
|
+
dump = {}
|
|
87
|
+
puts "Loading state dump from #{opts[:restore]}..."
|
|
88
|
+
IO.foreach opts[:restore] do |l|
|
|
89
|
+
l =~ /^(\S+) \((.*?)\)$/ or raise "Can't read dump line: #{l.inspect}"
|
|
90
|
+
mid, labels = $1, $2
|
|
91
|
+
dump[mid] = labels.to_set_of_symbols
|
|
92
|
+
end
|
|
93
|
+
puts "Read #{dump.size} entries from dump file."
|
|
94
|
+
dump
|
|
95
|
+
else
|
|
96
|
+
{}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
seen = {}
|
|
100
|
+
index.lock_interactively or exit
|
|
101
|
+
begin
|
|
102
|
+
index.load
|
|
103
|
+
|
|
104
|
+
if(s = Redwood::SourceManager.source_for Redwood::SentManager.source_uri)
|
|
105
|
+
Redwood::SentManager.source = s
|
|
106
|
+
else
|
|
107
|
+
Redwood::SourceManager.add_source Redwood::SentManager.default_source
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sources = if opts[:all_sources]
|
|
111
|
+
Redwood::SourceManager.sources
|
|
112
|
+
elsif ARGV.empty?
|
|
113
|
+
Redwood::SourceManager.usual_sources
|
|
114
|
+
else
|
|
115
|
+
ARGV.map do |uri|
|
|
116
|
+
Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
sources.each do |source|
|
|
121
|
+
puts "Scanning #{source}..."
|
|
122
|
+
num_added = num_updated = num_deleted = num_scanned = num_restored = 0
|
|
123
|
+
last_info_time = start_time = Time.now
|
|
124
|
+
|
|
125
|
+
Redwood::PollManager.poll_from source do |action,m,old_m,progress|
|
|
126
|
+
num_scanned += 1
|
|
127
|
+
if action == :delete
|
|
128
|
+
num_deleted += 1
|
|
129
|
+
puts "Deleting #{m.id}" if opts[:verbose]
|
|
130
|
+
elsif action == :add
|
|
131
|
+
seen[m.id] = true
|
|
132
|
+
|
|
133
|
+
## tweak source labels according to commandline arguments if necessary
|
|
134
|
+
m.labels.delete :inbox if opts[:archive]
|
|
135
|
+
m.labels.delete :unread if opts[:read]
|
|
136
|
+
m.labels += opts[:extra_labels].to_set_of_symbols(",")
|
|
137
|
+
|
|
138
|
+
## decide what to do based on message labels and the operation we're performing
|
|
139
|
+
dothis = case
|
|
140
|
+
when (op == :restore) && restored_state[m.id]
|
|
141
|
+
if old_m && (old_m.labels != restored_state[m.id])
|
|
142
|
+
num_restored += 1
|
|
143
|
+
m.labels = restored_state[m.id]
|
|
144
|
+
:update_message_state
|
|
145
|
+
elsif old_m.nil?
|
|
146
|
+
num_restored += 1
|
|
147
|
+
m.labels = restored_state[m.id]
|
|
148
|
+
:add_message
|
|
149
|
+
else
|
|
150
|
+
# labels are the same; don't do anything
|
|
151
|
+
end
|
|
152
|
+
when op == :discard
|
|
153
|
+
if old_m && (old_m.labels != m.labels)
|
|
154
|
+
:update_message_state
|
|
155
|
+
else
|
|
156
|
+
# labels are the same; don't do anything
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
if old_m
|
|
160
|
+
:update_message
|
|
161
|
+
else
|
|
162
|
+
:add_message
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
## now, actually do the operation
|
|
167
|
+
case dothis
|
|
168
|
+
when :add_message
|
|
169
|
+
puts "Adding new message #{source}##{m.source_info} with labels #{m.labels}" if opts[:verbose]
|
|
170
|
+
num_added += 1
|
|
171
|
+
when :update_message
|
|
172
|
+
puts "Updating message #{source}##{m.source_info}; labels #{old_m.labels} => #{m.labels}; offset #{old_m.source_info} => #{m.source_info}" if opts[:verbose]
|
|
173
|
+
num_updated += 1
|
|
174
|
+
when :update_message_state
|
|
175
|
+
puts "Changing flags for #{source}##{m.source_info} from #{old_m.labels} to #{m.labels}" if opts[:verbose]
|
|
176
|
+
num_updated += 1
|
|
177
|
+
end
|
|
178
|
+
else fail "sup-sync cannot handle :update's"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if Time.now - last_info_time > PROGRESS_UPDATE_INTERVAL
|
|
182
|
+
last_info_time = Time.now
|
|
183
|
+
elapsed = last_info_time - start_time
|
|
184
|
+
pctdone = progress * 100.0
|
|
185
|
+
remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
|
|
186
|
+
printf "## scanned %dm (~%.0f%%) @ %.1fm/s. %s elapsed, ~%s remaining\n", num_scanned, pctdone, num_scanned / elapsed, elapsed.to_time_s, remaining.to_time_s
|
|
187
|
+
end
|
|
188
|
+
next if opts[:dry_run]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
puts "Scanned #{num_scanned}, added #{num_added}, updated #{num_updated}, deleted #{num_deleted} messages from #{source}."
|
|
192
|
+
puts "Restored state on #{num_restored} (#{100.0 * num_restored / num_scanned}%) messages." if num_restored > 0
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
index.save
|
|
196
|
+
|
|
197
|
+
if opts[:optimize]
|
|
198
|
+
puts "Optimizing index..."
|
|
199
|
+
optt = time { index.optimize unless opts[:dry_run] }
|
|
200
|
+
puts "Optimized index of size #{index.size} in #{optt}s."
|
|
201
|
+
end
|
|
202
|
+
rescue Redwood::FatalSourceError => e
|
|
203
|
+
$stderr.puts "Sorry, I couldn't communicate with a source: #{e.message}"
|
|
204
|
+
rescue Exception => e
|
|
205
|
+
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
|
206
|
+
raise
|
|
207
|
+
ensure
|
|
208
|
+
Redwood::finish
|
|
209
|
+
index.unlock
|
|
210
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
5
|
+
|
|
6
|
+
require 'rubygems'
|
|
7
|
+
require 'trollop'
|
|
8
|
+
require "sup"
|
|
9
|
+
|
|
10
|
+
opts = Trollop::options do
|
|
11
|
+
version "sup-sync-back-maildir (sup #{Redwood::VERSION})"
|
|
12
|
+
banner <<EOS
|
|
13
|
+
Export Xapian entries to Maildir sources on disk.
|
|
14
|
+
|
|
15
|
+
This script parses the Xapian entries for a given Maildir source and renames
|
|
16
|
+
(changes maildir flags) e-mail files on disk according to the labels stored in
|
|
17
|
+
the index. It will export all the changes you made in Sup to your
|
|
18
|
+
Maildirs so that they can be propagated to your IMAP server with e.g. offlineimap.
|
|
19
|
+
|
|
20
|
+
The script also merges some Maildir flags into Sup such
|
|
21
|
+
as R (replied) and P (passed, forwarded), for instance suppose you
|
|
22
|
+
have an e-mail file like this: foo_bar:2,FRS (flags are favorite,
|
|
23
|
+
replied, seen) and its Xapian entry has labels 'starred', the merge
|
|
24
|
+
operation will add the 'replied' label to the Xapian entry.
|
|
25
|
+
|
|
26
|
+
If you choose not to merge (-m) you will lose information ('replied'), and in
|
|
27
|
+
the previous example the file will be renamed to foo_bar:2,FS.
|
|
28
|
+
|
|
29
|
+
Running this script is *strongly* recommended when setting the
|
|
30
|
+
"sync_back_to_maildir" option from false to true in config.yaml or changing the
|
|
31
|
+
"sync_back" flag to true for a source in sources.yaml.
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
sup-sync-back-maildir [options] <source>*
|
|
35
|
+
|
|
36
|
+
where <source>* is source URIs. If no source is given, the default behavior is
|
|
37
|
+
to sync back all Maildir sources marked as usual and that have not disabled
|
|
38
|
+
sync back using the configuration parameter sync_back = false in sources.yaml.
|
|
39
|
+
|
|
40
|
+
Options include:
|
|
41
|
+
EOS
|
|
42
|
+
opt :no_confirm, "Don't ask for confirmation before synchronizing", :default => false, :short => "n"
|
|
43
|
+
opt :no_merge, "Don't merge new supported Maildir flags (R and P)", :default => false, :short => "m"
|
|
44
|
+
opt :list_sources, "List your Maildir sources and exit", :default => false, :short => "l"
|
|
45
|
+
opt :unusual_sources_too, "Sync unusual sources too if no specific source information is given", :default => false, :short => "u"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def die msg
|
|
49
|
+
$stderr.puts "Error: #{msg}"
|
|
50
|
+
exit(-1)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Redwood::start true
|
|
54
|
+
index = Redwood::Index.init
|
|
55
|
+
index.lock_interactively or exit
|
|
56
|
+
index.load
|
|
57
|
+
|
|
58
|
+
## Force sync_back_to_maildir option otherwise nothing will happen
|
|
59
|
+
$config[:sync_back_to_maildir] = true
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
sync_performed = []
|
|
63
|
+
sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exists? Redwood::SYNC_OK_FN
|
|
64
|
+
sources = []
|
|
65
|
+
|
|
66
|
+
## Try to find out sources given in parameters
|
|
67
|
+
sources = ARGV.map do |uri|
|
|
68
|
+
s = Redwood::SourceManager.source_for(uri) or die "unknown source: #{uri}. Did you add it with sup-add first?"
|
|
69
|
+
s.is_a?(Redwood::Maildir) or die "#{uri} is not a Maildir source."
|
|
70
|
+
s.sync_back_enabled? or die "#{uri} has disabled sync back - check your configuration."
|
|
71
|
+
s
|
|
72
|
+
end unless opts[:list_sources]
|
|
73
|
+
|
|
74
|
+
## Otherwise, check all sources in sources.yaml
|
|
75
|
+
if sources.empty? or opts[:list_sources] == true
|
|
76
|
+
if opts[:unusual_sources_too]
|
|
77
|
+
sources = Redwood::SourceManager.sources.select do |s|
|
|
78
|
+
s.is_a? Redwood::Maildir and s.sync_back_enabled?
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
sources = Redwood::SourceManager.usual_sources.select do |s|
|
|
82
|
+
s.is_a? Redwood::Maildir and s.sync_back_enabled?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if opts[:list_sources] == true
|
|
88
|
+
sources.each do |s|
|
|
89
|
+
puts "id: #{s.id}, uri: #{s.uri}"
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
sources.each do |s|
|
|
93
|
+
if opts[:no_confirm] == false
|
|
94
|
+
print "Are you sure you want to synchronize '#{s.uri}'? (Y/n) "
|
|
95
|
+
next if STDIN.gets.chomp.downcase == 'n'
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
infos = Enumerator.new(index, :each_source_info, s.id).to_a
|
|
99
|
+
counter = 0
|
|
100
|
+
infos.each do |info|
|
|
101
|
+
print "\rSynchronizing '#{s.uri}'... #{((counter += 1)/infos.size.to_f*100).to_i}%"
|
|
102
|
+
index.each_message({:location => [s.id, info]}, false) do |m|
|
|
103
|
+
if opts[:no_merge] == false
|
|
104
|
+
m.merge_labels_from_locations [:replied, :forwarded]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if Redwood::Index.message_joining_killed? m
|
|
108
|
+
m.labels += [:killed]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
index.save_message m
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
print "\n"
|
|
115
|
+
sync_performed << s.uri
|
|
116
|
+
end
|
|
117
|
+
## Write a flag file to tell sup that the synchronization has been performed
|
|
118
|
+
File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(sync_performed.join("\n")) }
|
|
119
|
+
end
|
|
120
|
+
rescue Exception => e
|
|
121
|
+
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
|
122
|
+
raise
|
|
123
|
+
ensure
|
|
124
|
+
index.save_index
|
|
125
|
+
Redwood::finish
|
|
126
|
+
index.unlock
|
|
127
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'trollop'
|
|
7
|
+
require "sup"
|
|
8
|
+
|
|
9
|
+
class Float
|
|
10
|
+
def to_s; sprintf '%.2f', self; end
|
|
11
|
+
def to_time_s
|
|
12
|
+
infinite? ? "unknown" : super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Numeric
|
|
17
|
+
def to_time_s
|
|
18
|
+
i = to_i
|
|
19
|
+
sprintf "%d:%02d:%02d", i / 3600, (i / 60) % 60, i % 60
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def time
|
|
24
|
+
startt = Time.now
|
|
25
|
+
yield
|
|
26
|
+
Time.now - startt
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
opts = Trollop::options do
|
|
30
|
+
version "sup-tweak-labels (sup #{Redwood::VERSION})"
|
|
31
|
+
banner <<EOS
|
|
32
|
+
Batch modification of message state for messages already in the index.
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
sup-tweak-labels [options] <source>*
|
|
36
|
+
|
|
37
|
+
where <source>* is zero or more source URIs. Supported source URI schemes can
|
|
38
|
+
be seen by running "sup-add --help".
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
EOS
|
|
42
|
+
opt :add, "One or more labels (comma-separated) to add to every message from the specified sources", :default => ""
|
|
43
|
+
opt :remove, "One or more labels (comma-separated) to remove from every message from the specified sources, if those labels are present", :default => ""
|
|
44
|
+
opt :query, "A Sup search query", :type => String
|
|
45
|
+
|
|
46
|
+
text <<EOS
|
|
47
|
+
|
|
48
|
+
Other options:
|
|
49
|
+
EOS
|
|
50
|
+
opt :verbose, "Print message ids as they're processed."
|
|
51
|
+
opt :very_verbose, "Print message names and subjects as they're processed."
|
|
52
|
+
opt :all_sources, "Scan over all sources.", :short => :none
|
|
53
|
+
opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
|
|
54
|
+
opt :no_sync_back, "Do not sync back to the original Maildir."
|
|
55
|
+
opt :version, "Show version information", :short => :none
|
|
56
|
+
end
|
|
57
|
+
opts[:verbose] = true if opts[:very_verbose]
|
|
58
|
+
|
|
59
|
+
add_labels = opts[:add].to_set_of_symbols ","
|
|
60
|
+
remove_labels = opts[:remove].to_set_of_symbols ","
|
|
61
|
+
|
|
62
|
+
Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
|
|
63
|
+
|
|
64
|
+
Redwood::start
|
|
65
|
+
index = Redwood::Index.init
|
|
66
|
+
index.lock_interactively or exit
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
index.load
|
|
70
|
+
|
|
71
|
+
source_ids = if opts[:all_sources]
|
|
72
|
+
Redwood::SourceManager.sources
|
|
73
|
+
else
|
|
74
|
+
ARGV.map do |uri|
|
|
75
|
+
Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
|
76
|
+
end
|
|
77
|
+
end.map { |s| s.id }
|
|
78
|
+
Trollop::die "nothing to do: no sources" if source_ids.empty?
|
|
79
|
+
|
|
80
|
+
query = "(" + source_ids.map { |id| "source_id:#{id}" }.join(" OR ") + ")"
|
|
81
|
+
if add_labels.empty?
|
|
82
|
+
## if all we're doing is removing labels, we can further restrict the
|
|
83
|
+
## query to only messages with those labels
|
|
84
|
+
query += " (" + remove_labels.map { |l| "label:#{l}" }.join(" OR ") + ")"
|
|
85
|
+
end
|
|
86
|
+
query += ' ' + opts[:query] if opts[:query]
|
|
87
|
+
|
|
88
|
+
parsed_query = index.parse_query query
|
|
89
|
+
parsed_query.merge! :load_spam => true, :load_deleted => true, :load_killed => true
|
|
90
|
+
ids = index.to_enum(:each_id, parsed_query)
|
|
91
|
+
num_total = index.num_results_for parsed_query
|
|
92
|
+
|
|
93
|
+
$stderr.puts "Found #{num_total} documents across #{source_ids.length} sources. Scanning..."
|
|
94
|
+
|
|
95
|
+
num_changed = num_scanned = 0
|
|
96
|
+
last_info_time = start_time = Time.now
|
|
97
|
+
ids.each do |id|
|
|
98
|
+
num_scanned += 1
|
|
99
|
+
|
|
100
|
+
m = index.build_message id
|
|
101
|
+
old_labels = m.labels.dup
|
|
102
|
+
|
|
103
|
+
m.labels += add_labels
|
|
104
|
+
m.labels -= remove_labels
|
|
105
|
+
|
|
106
|
+
unless m.labels == old_labels
|
|
107
|
+
num_changed += 1
|
|
108
|
+
puts "From #{m.from}, subject: #{m.subj}" if opts[:very_verbose]
|
|
109
|
+
puts "#{m.id}: {#{old_labels.to_a.join ','}} => {#{m.labels.to_a.join ','}}" if opts[:verbose]
|
|
110
|
+
puts if opts[:very_verbose]
|
|
111
|
+
unless opts[:dry_run]
|
|
112
|
+
index.update_message_state [m, false]
|
|
113
|
+
m.sync_back unless opts[:no_sync_back]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if Time.now - last_info_time > 60
|
|
118
|
+
last_info_time = Time.now
|
|
119
|
+
elapsed = last_info_time - start_time
|
|
120
|
+
pctdone = 100.0 * num_scanned.to_f / num_total.to_f
|
|
121
|
+
remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
|
|
122
|
+
$stderr.puts "## #{num_scanned} (#{pctdone}%) read; #{elapsed.to_time_s} elapsed; #{remaining.to_time_s} remaining"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
$stderr.puts "Scanned #{num_scanned} / #{num_total} messages and changed #{num_changed}."
|
|
126
|
+
|
|
127
|
+
unless num_changed == 0
|
|
128
|
+
$stderr.puts "Optimizing index..."
|
|
129
|
+
index.optimize unless opts[:dry_run]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
rescue Exception => e
|
|
133
|
+
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
|
134
|
+
raise
|
|
135
|
+
ensure
|
|
136
|
+
index.save
|
|
137
|
+
Redwood::finish
|
|
138
|
+
index.unlock
|
|
139
|
+
end
|
|
140
|
+
|