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-config
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'highline/import'
|
|
7
|
+
require 'trollop'
|
|
8
|
+
require "sup"
|
|
9
|
+
|
|
10
|
+
$opts = Trollop::options do
|
|
11
|
+
version "sup-config (sup #{Redwood::VERSION})"
|
|
12
|
+
banner <<EOS
|
|
13
|
+
Interactive configuration tool for Sup. Won't destroy existing
|
|
14
|
+
configuration.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
sup-config
|
|
18
|
+
|
|
19
|
+
No options.
|
|
20
|
+
EOS
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def axe q, default=nil
|
|
24
|
+
question = if default && !default.empty?
|
|
25
|
+
"#{q} (enter for \"#{default}\"): "
|
|
26
|
+
else
|
|
27
|
+
"#{q}: "
|
|
28
|
+
end
|
|
29
|
+
ans = ask question
|
|
30
|
+
ans.empty? ? default : ans.to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def axe_yes q, default="n"
|
|
34
|
+
axe(q, default) =~ /^y|yes$/i
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_cmd cmd
|
|
38
|
+
(ENV["RUBY_INVOCATION"] ? ENV["RUBY_INVOCATION"] + " " : "") + File.join(File.dirname($0), cmd)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def add_source
|
|
42
|
+
require "sup/util/uri"
|
|
43
|
+
|
|
44
|
+
type = nil
|
|
45
|
+
|
|
46
|
+
say "Ok, adding a new source."
|
|
47
|
+
choose do |menu|
|
|
48
|
+
menu.prompt = "What type of mail source is it? "
|
|
49
|
+
menu.choice("mbox file") { type = :mbox }
|
|
50
|
+
menu.choice("maildir directory") { type = :maildir }
|
|
51
|
+
menu.choice("Get me out of here!") { return }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
while true do
|
|
55
|
+
say "Ok, now for the details."
|
|
56
|
+
|
|
57
|
+
default_labels, components = case type
|
|
58
|
+
when :mbox
|
|
59
|
+
$last_fn ||= ENV["MAIL"]
|
|
60
|
+
fn = axe "What's the full path to the mbox file?", $last_fn
|
|
61
|
+
return if fn.nil? || fn.empty?
|
|
62
|
+
|
|
63
|
+
$last_fn = fn
|
|
64
|
+
[Redwood::MBox.suggest_labels_for(fn),
|
|
65
|
+
{ :scheme => "mbox", :path => fn }]
|
|
66
|
+
when :maildir
|
|
67
|
+
$last_fn ||= ENV["MAIL"]
|
|
68
|
+
fn = axe "What's the full path to the maildir directory?", $last_fn
|
|
69
|
+
return if fn.nil? || fn.empty?
|
|
70
|
+
|
|
71
|
+
$last_fn = fn
|
|
72
|
+
[Redwood::Maildir.suggest_labels_for(fn),
|
|
73
|
+
{ :scheme => "maildir", :path => fn }]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
uri = begin
|
|
77
|
+
Redwood::Util::Uri.build components
|
|
78
|
+
rescue URI::Error => e
|
|
79
|
+
say "Whoopsie! I couldn't build a URI from that: #{e.message}"
|
|
80
|
+
if axe_yes("Try again?") then next else return end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
say "I'm going to add this source: #{uri}"
|
|
84
|
+
unless axe("Does that look right?", "y") =~ /^y|yes$/i
|
|
85
|
+
if axe_yes("Try again?") then next else return end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
usual = axe_yes "Does this source ever receive new messages?", "y"
|
|
89
|
+
archive = usual ? axe_yes("Should new messages be automatically archived? (I.e. not appear in your inbox, though still be accessible via search.)") : false
|
|
90
|
+
|
|
91
|
+
sync_back = (type == :maildir) ? axe_yes("Should the original Maildir messages be modified to reflect changes like read status, starred messages, etc.?", "y") : false
|
|
92
|
+
|
|
93
|
+
labels_str = axe("Enter any labels to be automatically added to all messages from this source, separated by spaces (or 'none')", default_labels.join(","))
|
|
94
|
+
|
|
95
|
+
labels = if labels_str =~ /^\s*none\s*$/i
|
|
96
|
+
nil
|
|
97
|
+
else
|
|
98
|
+
labels_str.split(/\s+/)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
cmd = build_cmd "sup-add"
|
|
102
|
+
cmd += " --unusual" unless usual
|
|
103
|
+
cmd += " --archive" if archive
|
|
104
|
+
cmd += " --no-sync-back" unless sync_back
|
|
105
|
+
cmd += " --labels=#{labels.join(',')}" if labels && !labels.empty?
|
|
106
|
+
cmd += " #{uri}"
|
|
107
|
+
|
|
108
|
+
puts "Ok, trying to run \"#{cmd}\"..."
|
|
109
|
+
|
|
110
|
+
system cmd
|
|
111
|
+
if $?.success?
|
|
112
|
+
say "Great! Added!"
|
|
113
|
+
break
|
|
114
|
+
else
|
|
115
|
+
say "Rats, that failed. You may have to do it manually."
|
|
116
|
+
if axe_yes("Try again?") then next else return end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
$terminal.wrap_at = :auto
|
|
122
|
+
Redwood::start
|
|
123
|
+
index = Redwood::Index.init
|
|
124
|
+
Redwood::SourceManager.load_sources
|
|
125
|
+
|
|
126
|
+
say <<EOS
|
|
127
|
+
Howdy neighbor! This here's sup-config, ready to help you jack in to
|
|
128
|
+
the next generation of digital cyberspace: the text-based email
|
|
129
|
+
program. Get ready to be the envy of everyone in your internets
|
|
130
|
+
with your amazing keyboarding skills! Jump from email to email with
|
|
131
|
+
nary a click of the mouse!
|
|
132
|
+
|
|
133
|
+
Just answer these simple questions and you'll be on your way.
|
|
134
|
+
|
|
135
|
+
EOS
|
|
136
|
+
|
|
137
|
+
account = $config[:accounts][:default]
|
|
138
|
+
|
|
139
|
+
name = axe "What's your name?", account[:name]
|
|
140
|
+
email = axe "What's your (primary) email address?", account[:email]
|
|
141
|
+
|
|
142
|
+
say "Ok, your from header will look like this:"
|
|
143
|
+
say " From: #{name} <#{email}>"
|
|
144
|
+
|
|
145
|
+
say "\nDo you have any alternate email addresses that also receive email?"
|
|
146
|
+
say "If so, enter them now, separated by spaces."
|
|
147
|
+
alts = axe("Alternate email addresses", account[:alternates].join(" ")).split(/\s+/)
|
|
148
|
+
|
|
149
|
+
sigfn = axe "What file contains your signature?", account[:signature]
|
|
150
|
+
editor = axe "What editor would you like to use?", $config[:editor]
|
|
151
|
+
|
|
152
|
+
time_mode = axe "Would like to display time in 12h (type 12h) or in 24h (type 24h)?", $config[:time_mode]
|
|
153
|
+
|
|
154
|
+
$config[:accounts][:default][:name] = name
|
|
155
|
+
$config[:accounts][:default][:email] = email
|
|
156
|
+
$config[:accounts][:default][:alternates] = alts
|
|
157
|
+
$config[:accounts][:default][:signature] = sigfn
|
|
158
|
+
$config[:editor] = editor
|
|
159
|
+
$config[:time_mode] = time_mode
|
|
160
|
+
|
|
161
|
+
done = false
|
|
162
|
+
until done
|
|
163
|
+
say "\nNow, we'll tell Sup where to find all your email."
|
|
164
|
+
Redwood::SourceManager.load_sources
|
|
165
|
+
say "Current sources:"
|
|
166
|
+
if Redwood::SourceManager.sources.empty?
|
|
167
|
+
say " No sources!"
|
|
168
|
+
else
|
|
169
|
+
Redwood::SourceManager.sources.each { |s| puts "* #{s}" }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
say "\n"
|
|
173
|
+
choose do |menu|
|
|
174
|
+
menu.prompt = "Your wish? "
|
|
175
|
+
menu.choice("Add a new source.") { add_source }
|
|
176
|
+
menu.choice("Done adding sources!") { done = true }
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
say "\nSup needs to know where to store your sent messages."
|
|
181
|
+
say "Only sources capable of storing mail will be listed.\n\n"
|
|
182
|
+
|
|
183
|
+
Redwood::SourceManager.load_sources
|
|
184
|
+
if Redwood::SourceManager.sources.empty?
|
|
185
|
+
say "\nUsing the default sup://sent, since you haven't configured other sources yet."
|
|
186
|
+
$config[:sent_source] = 'sup://sent'
|
|
187
|
+
else
|
|
188
|
+
# this handles the event that source.yaml already contains the SentLoader
|
|
189
|
+
# source.
|
|
190
|
+
have_sup_sent = false
|
|
191
|
+
|
|
192
|
+
choose do |menu|
|
|
193
|
+
menu.prompt = "Store my sent mail in? "
|
|
194
|
+
|
|
195
|
+
menu.choice('Default (an mbox in ~/.sup, aka sup://sent)') { $config[:sent_source] = 'sup://sent'} unless have_sup_sent
|
|
196
|
+
|
|
197
|
+
valid_sents = Redwood::SourceManager.sources.each do |s|
|
|
198
|
+
have_sup_sent = true if s.to_s.eql?('sup://sent')
|
|
199
|
+
menu.choice(s.to_s) { $config[:sent_source] = s.to_s } if s.respond_to? :store_message
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
Redwood::save_yaml_obj $config, Redwood::CONFIG_FN, false, true
|
|
205
|
+
|
|
206
|
+
say "Ok, I've saved you up a nice lil' #{Redwood::CONFIG_FN}."
|
|
207
|
+
|
|
208
|
+
say <<EOS
|
|
209
|
+
|
|
210
|
+
The final step is to import all your messages into the Sup index.
|
|
211
|
+
Depending on how many messages are in the sources, this could take
|
|
212
|
+
quite a while.
|
|
213
|
+
|
|
214
|
+
EOS
|
|
215
|
+
|
|
216
|
+
if axe_yes "Run sup-sync to import all messages now?"
|
|
217
|
+
while true
|
|
218
|
+
cmd = build_cmd("sup-sync") + " --all-sources"
|
|
219
|
+
puts "Ok, trying to run \"#{cmd}\"..."
|
|
220
|
+
system cmd
|
|
221
|
+
if $?.success?
|
|
222
|
+
say "Great! It worked!"
|
|
223
|
+
break
|
|
224
|
+
else
|
|
225
|
+
say "Rats, that failed. You may have to do it manually."
|
|
226
|
+
if axe_yes("Try again?") then next else break end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
index.load
|
|
232
|
+
|
|
233
|
+
say <<EOS
|
|
234
|
+
|
|
235
|
+
Okee doke, you've got yourself an index of #{index.size} messages. Looks
|
|
236
|
+
like you're ready to jack in to cyberspace there, cowboy.
|
|
237
|
+
|
|
238
|
+
Just one last command:
|
|
239
|
+
|
|
240
|
+
#{build_cmd "sup"}
|
|
241
|
+
|
|
242
|
+
Have fun!
|
|
243
|
+
EOS
|
data/bin/sup-dump
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require 'rubygems'
|
|
6
|
+
require 'xapian'
|
|
7
|
+
require 'trollop'
|
|
8
|
+
require 'set'
|
|
9
|
+
|
|
10
|
+
BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
|
|
11
|
+
|
|
12
|
+
$opts = Trollop::options do
|
|
13
|
+
version "sup-dump"
|
|
14
|
+
banner <<EOS
|
|
15
|
+
Dumps all message state from the sup index to standard out. You can
|
|
16
|
+
later use sup-sync --restored --restore <filename> to recover the index.
|
|
17
|
+
|
|
18
|
+
This tool is primarily useful in the event that a Sup upgrade breaks index
|
|
19
|
+
format compatibility.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
sup-dump > <filename>
|
|
23
|
+
sup-dump | bzip2 > <filename> # even better
|
|
24
|
+
EOS
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
xapian = Xapian::Database.new File.join(BASE_DIR, 'xapian')
|
|
28
|
+
version = xapian.get_metadata 'rescue-version'
|
|
29
|
+
version = '0' if version.empty?
|
|
30
|
+
|
|
31
|
+
case version
|
|
32
|
+
when '0'
|
|
33
|
+
xapian.postlist('Kmail').each do |x|
|
|
34
|
+
begin
|
|
35
|
+
entry = Marshal.load(xapian.document(x.docid).data)
|
|
36
|
+
puts "#{entry[:message_id]} (#{entry[:labels].sort_by { |l| l.to_s } * ' '})"
|
|
37
|
+
rescue
|
|
38
|
+
$stderr.puts "failed to dump document #{x.docid}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
else
|
|
42
|
+
abort "this sup-dump version doesn't understand your index"
|
|
43
|
+
end
|
data/bin/sup-import-dump
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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 AbortExecution < SystemExit
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
opts = Trollop::options do
|
|
16
|
+
version "sup-import-dump (sup #{Redwood::VERSION})"
|
|
17
|
+
banner <<EOS
|
|
18
|
+
Imports message state previously exported by sup-dump into the index.
|
|
19
|
+
sup-import-dump operates on the index only, so the messages must have already
|
|
20
|
+
been added using sup-sync. If you need to recreate the index, see sup-sync
|
|
21
|
+
--restore <filename> instead.
|
|
22
|
+
|
|
23
|
+
Messages not mentioned in the dump file will not be modified.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
sup-import-dump [options] <dump file>
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
EOS
|
|
30
|
+
opt :verbose, "Print message ids as they're processed."
|
|
31
|
+
opt :ignore_missing, "Silently skip over messages that are not in the index."
|
|
32
|
+
opt :warn_missing, "Warn about messages that are not in the index, but continue."
|
|
33
|
+
opt :abort_missing, "Abort on encountering messages that are not in the index. (default)"
|
|
34
|
+
opt :atomic, "Use transaction to apply all changes atomically."
|
|
35
|
+
opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
|
|
36
|
+
opt :version, "Show version information", :short => :none
|
|
37
|
+
|
|
38
|
+
conflicts :ignore_missing, :warn_missing, :abort_missing
|
|
39
|
+
end
|
|
40
|
+
Trollop::die "No dump file given" if ARGV.empty?
|
|
41
|
+
Trollop::die "Extra arguments given" if ARGV.length > 1
|
|
42
|
+
dump_name = ARGV.shift
|
|
43
|
+
missing_action = [:ignore_missing, :warn_missing, :abort_missing].find { |x| opts[x] } || :abort_missing
|
|
44
|
+
|
|
45
|
+
Redwood::start
|
|
46
|
+
index = Redwood::Index.init
|
|
47
|
+
|
|
48
|
+
index.lock_interactively or exit
|
|
49
|
+
begin
|
|
50
|
+
num_read = 0
|
|
51
|
+
num_changed = 0
|
|
52
|
+
index.load
|
|
53
|
+
index.begin_transaction if opts[:atomic]
|
|
54
|
+
|
|
55
|
+
IO.foreach dump_name do |l|
|
|
56
|
+
l =~ /^(\S+) \((.*?)\)$/ or raise "Can't read dump line: #{l.inspect}"
|
|
57
|
+
mid, labels = $1, $2
|
|
58
|
+
num_read += 1
|
|
59
|
+
|
|
60
|
+
unless index.contains_id? mid
|
|
61
|
+
if missing_action == :abort_missing
|
|
62
|
+
$stderr.puts "Message #{mid} not found in index, aborting."
|
|
63
|
+
raise AbortExecution, 10
|
|
64
|
+
elsif missing_action == :warn_missing
|
|
65
|
+
$stderr.puts "Message #{mid} not found in index, skipping."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
m = index.build_message mid
|
|
72
|
+
new_labels = labels.to_set_of_symbols
|
|
73
|
+
|
|
74
|
+
if m.labels == new_labels
|
|
75
|
+
puts "#{mid} unchanged" if opts[:verbose]
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts "Changing flags for #{mid} from '#{m.labels.to_a * ' '}' to '#{new_labels.to_a * ' '}'" if opts[:verbose]
|
|
80
|
+
num_changed += 1
|
|
81
|
+
|
|
82
|
+
next if opts[:dry_run]
|
|
83
|
+
|
|
84
|
+
m.labels = new_labels
|
|
85
|
+
index.update_message_state m
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
index.commit_transaction if opts[:atomic]
|
|
89
|
+
puts "Updated #{num_changed} of #{num_read} messages."
|
|
90
|
+
rescue AbortExecution
|
|
91
|
+
index.cancel_transaction if opts[:atomic]
|
|
92
|
+
raise
|
|
93
|
+
rescue Exception => e
|
|
94
|
+
index.cancel_transaction if opts[:atomic]
|
|
95
|
+
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
|
96
|
+
raise
|
|
97
|
+
ensure
|
|
98
|
+
index.save_index unless opts[:atomic]
|
|
99
|
+
Redwood::finish
|
|
100
|
+
index.unlock
|
|
101
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require "sup"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
|
|
8
|
+
if RUBY_VERSION >= "2.1"
|
|
9
|
+
puts "YAML migration is deprecated by Ruby 2.1 and newer."
|
|
10
|
+
exit
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Redwood.start
|
|
14
|
+
|
|
15
|
+
fn = Redwood::SOURCE_FN
|
|
16
|
+
FileUtils.cp fn, "#{fn}.syck_bak"
|
|
17
|
+
|
|
18
|
+
Redwood::SourceManager.load_sources fn
|
|
19
|
+
Redwood::SourceManager.save_sources fn, true
|
|
20
|
+
|
|
21
|
+
Redwood.finish
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require 'optparse'
|
|
6
|
+
|
|
7
|
+
$opts = {
|
|
8
|
+
:unusual => false,
|
|
9
|
+
:archive => false,
|
|
10
|
+
:scan_num => 10,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
OPTIONPARSERSUCKS = "\n" + " " * 38
|
|
15
|
+
OptionParser.new do |opts|
|
|
16
|
+
opts.banner = <<EOS
|
|
17
|
+
Usage: sup-recover-sources [options] <source>+
|
|
18
|
+
|
|
19
|
+
Rebuilds a lost sources.yaml file by reading messages from a list of
|
|
20
|
+
sources and determining, for each source, the most prevalent
|
|
21
|
+
'source_id' field of messages from that source in the index.
|
|
22
|
+
|
|
23
|
+
The only non-deterministic component to this is that if the same
|
|
24
|
+
message appears in multiple sources, those sources may be
|
|
25
|
+
mis-diagnosed by this program.
|
|
26
|
+
|
|
27
|
+
If the first N messages (--scan-num below) all have the same source_id
|
|
28
|
+
in the index, the source will be added to sources.yaml. Otherwise, the
|
|
29
|
+
distribution will be printed, and you will have to add it by hand.
|
|
30
|
+
|
|
31
|
+
The offset pointer into the sources will be set to the end of the source,
|
|
32
|
+
so you will have to run sup-import --rebuild for each new source after
|
|
33
|
+
doing this.
|
|
34
|
+
|
|
35
|
+
Options include:
|
|
36
|
+
EOS
|
|
37
|
+
|
|
38
|
+
opts.on("--unusual", "Mark sources as 'unusual'. Only usual#{OPTIONPARSERSUCKS}sources will be polled by hand. Default:#{OPTIONPARSERSUCKS}#{$opts[:unusual]}.") { $opts[:unusual] = true }
|
|
39
|
+
|
|
40
|
+
opts.on("--archive", "Mark sources as 'archive'. New messages#{OPTIONPARSERSUCKS}from these sources will not appear in#{OPTIONPARSERSUCKS}the inbox. Default: #{$opts[:archive]}.") { $opts[:archive] = true }
|
|
41
|
+
|
|
42
|
+
opts.on("--scan-num N", Integer, "Number of messages to scan per source.#{OPTIONPARSERSUCKS}Default: #{$opts[:scan_num]}.") do |n|
|
|
43
|
+
$opts[:scan_num] = n
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
|
47
|
+
puts opts
|
|
48
|
+
exit
|
|
49
|
+
end
|
|
50
|
+
end.parse(ARGV)
|
|
51
|
+
|
|
52
|
+
require "sup"
|
|
53
|
+
Redwood::start
|
|
54
|
+
puts "loading index..."
|
|
55
|
+
index = Redwood::Index.init
|
|
56
|
+
index.load
|
|
57
|
+
puts "loaded index of #{index.size} messages"
|
|
58
|
+
|
|
59
|
+
ARGV.each do |fn|
|
|
60
|
+
next if Redwood::SourceManager.source_for fn
|
|
61
|
+
|
|
62
|
+
## TODO: merge this code with the same snippet in import
|
|
63
|
+
source = Redwood::MBox.new(fn, nil, !$opts[:unusual], $opts[:archive])
|
|
64
|
+
|
|
65
|
+
source_ids = Hash.new 0
|
|
66
|
+
count = 0
|
|
67
|
+
source.each do |offset, labels|
|
|
68
|
+
m = Redwood::Message.new :source => source, :source_info => offset
|
|
69
|
+
m.load_from_source!
|
|
70
|
+
source_id = Redwood::SourceManager.source_for_id m.id
|
|
71
|
+
next unless source_id
|
|
72
|
+
source_ids[source_id] += 1
|
|
73
|
+
count += 1
|
|
74
|
+
break if count == $opts[:scan_num]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if source_ids.size == 1
|
|
78
|
+
id = source_ids.keys.first.to_i
|
|
79
|
+
puts "assigned #{source} to #{source_ids.keys.first}"
|
|
80
|
+
source.id = id
|
|
81
|
+
Redwood::SourceManager.add_source source
|
|
82
|
+
else
|
|
83
|
+
puts ">> unable to determine #{source}: #{source_ids.inspect}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
index.save
|