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.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +12 -0
  4. data/CONTRIBUTORS +84 -0
  5. data/Gemfile +3 -0
  6. data/HACKING +42 -0
  7. data/History.txt +361 -0
  8. data/LICENSE +280 -0
  9. data/README.md +70 -0
  10. data/Rakefile +12 -0
  11. data/ReleaseNotes +231 -0
  12. data/bin/sup +434 -0
  13. data/bin/sup-add +118 -0
  14. data/bin/sup-config +243 -0
  15. data/bin/sup-dump +43 -0
  16. data/bin/sup-import-dump +101 -0
  17. data/bin/sup-psych-ify-config-files +21 -0
  18. data/bin/sup-recover-sources +87 -0
  19. data/bin/sup-sync +210 -0
  20. data/bin/sup-sync-back-maildir +127 -0
  21. data/bin/sup-tweak-labels +140 -0
  22. data/contrib/colorpicker.rb +100 -0
  23. data/contrib/completion/_sup.zsh +114 -0
  24. data/devel/console.sh +3 -0
  25. data/devel/count-loc.sh +3 -0
  26. data/devel/load-index.rb +9 -0
  27. data/devel/profile.rb +12 -0
  28. data/devel/start-console.rb +5 -0
  29. data/doc/FAQ.txt +119 -0
  30. data/doc/Hooks.txt +79 -0
  31. data/doc/Philosophy.txt +69 -0
  32. data/lib/sup.rb +467 -0
  33. data/lib/sup/account.rb +90 -0
  34. data/lib/sup/buffer.rb +768 -0
  35. data/lib/sup/colormap.rb +239 -0
  36. data/lib/sup/contact.rb +67 -0
  37. data/lib/sup/crypto.rb +461 -0
  38. data/lib/sup/draft.rb +119 -0
  39. data/lib/sup/hook.rb +159 -0
  40. data/lib/sup/horizontal_selector.rb +59 -0
  41. data/lib/sup/idle.rb +42 -0
  42. data/lib/sup/index.rb +882 -0
  43. data/lib/sup/interactive_lock.rb +89 -0
  44. data/lib/sup/keymap.rb +140 -0
  45. data/lib/sup/label.rb +87 -0
  46. data/lib/sup/logger.rb +77 -0
  47. data/lib/sup/logger/singleton.rb +10 -0
  48. data/lib/sup/maildir.rb +257 -0
  49. data/lib/sup/mbox.rb +187 -0
  50. data/lib/sup/message.rb +803 -0
  51. data/lib/sup/message_chunks.rb +328 -0
  52. data/lib/sup/mode.rb +140 -0
  53. data/lib/sup/modes/buffer_list_mode.rb +50 -0
  54. data/lib/sup/modes/completion_mode.rb +55 -0
  55. data/lib/sup/modes/compose_mode.rb +38 -0
  56. data/lib/sup/modes/console_mode.rb +125 -0
  57. data/lib/sup/modes/contact_list_mode.rb +148 -0
  58. data/lib/sup/modes/edit_message_async_mode.rb +110 -0
  59. data/lib/sup/modes/edit_message_mode.rb +728 -0
  60. data/lib/sup/modes/file_browser_mode.rb +109 -0
  61. data/lib/sup/modes/forward_mode.rb +82 -0
  62. data/lib/sup/modes/help_mode.rb +19 -0
  63. data/lib/sup/modes/inbox_mode.rb +85 -0
  64. data/lib/sup/modes/label_list_mode.rb +138 -0
  65. data/lib/sup/modes/label_search_results_mode.rb +38 -0
  66. data/lib/sup/modes/line_cursor_mode.rb +203 -0
  67. data/lib/sup/modes/log_mode.rb +57 -0
  68. data/lib/sup/modes/person_search_results_mode.rb +12 -0
  69. data/lib/sup/modes/poll_mode.rb +19 -0
  70. data/lib/sup/modes/reply_mode.rb +228 -0
  71. data/lib/sup/modes/resume_mode.rb +52 -0
  72. data/lib/sup/modes/scroll_mode.rb +252 -0
  73. data/lib/sup/modes/search_list_mode.rb +204 -0
  74. data/lib/sup/modes/search_results_mode.rb +59 -0
  75. data/lib/sup/modes/text_mode.rb +76 -0
  76. data/lib/sup/modes/thread_index_mode.rb +1033 -0
  77. data/lib/sup/modes/thread_view_mode.rb +941 -0
  78. data/lib/sup/person.rb +134 -0
  79. data/lib/sup/poll.rb +272 -0
  80. data/lib/sup/rfc2047.rb +56 -0
  81. data/lib/sup/search.rb +110 -0
  82. data/lib/sup/sent.rb +58 -0
  83. data/lib/sup/service/label_service.rb +45 -0
  84. data/lib/sup/source.rb +244 -0
  85. data/lib/sup/tagger.rb +50 -0
  86. data/lib/sup/textfield.rb +253 -0
  87. data/lib/sup/thread.rb +452 -0
  88. data/lib/sup/time.rb +93 -0
  89. data/lib/sup/undo.rb +38 -0
  90. data/lib/sup/update.rb +30 -0
  91. data/lib/sup/util.rb +747 -0
  92. data/lib/sup/util/ncurses.rb +274 -0
  93. data/lib/sup/util/path.rb +9 -0
  94. data/lib/sup/util/query.rb +17 -0
  95. data/lib/sup/util/uri.rb +15 -0
  96. data/lib/sup/version.rb +3 -0
  97. data/sup.gemspec +53 -0
  98. data/test/dummy_source.rb +61 -0
  99. data/test/gnupg_test_home/gpg.conf +1 -0
  100. data/test/gnupg_test_home/pubring.gpg +0 -0
  101. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  102. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  103. data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
  104. data/test/gnupg_test_home/secring.gpg +0 -0
  105. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
  106. data/test/gnupg_test_home/trustdb.gpg +0 -0
  107. data/test/integration/test_label_service.rb +18 -0
  108. data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
  109. data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
  110. data/test/messages/missing-line.eml +9 -0
  111. data/test/test_crypto.rb +109 -0
  112. data/test/test_header_parsing.rb +168 -0
  113. data/test/test_helper.rb +7 -0
  114. data/test/test_message.rb +532 -0
  115. data/test/test_messages_dir.rb +147 -0
  116. data/test/test_yaml_migration.rb +85 -0
  117. data/test/test_yaml_regressions.rb +17 -0
  118. data/test/unit/service/test_label_service.rb +19 -0
  119. data/test/unit/test_horizontal_selector.rb +40 -0
  120. data/test/unit/util/test_query.rb +46 -0
  121. data/test/unit/util/test_string.rb +57 -0
  122. data/test/unit/util/test_uri.rb +19 -0
  123. metadata +423 -0
@@ -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
@@ -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
@@ -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