sup 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
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