5 require 'highline/import'
9 Thread.abort_on_exception = true # make debugging possible
12 def to_s; sprintf '%.2f', self; end
18 sprintf "%d:%02d:%02d", i / 3600, (i / 60) % 60, i % 60
30 Loads messages into the Sup index, adding sources as needed to the
34 sup-import [options] <source>*
35 where <source>* is zero or more source descriptions (e.g., mbox
36 filenames on disk, or imap/imaps URIs).
38 If the sources listed are not already in the Sup source list,
39 they will be added to it, as parameterized by the following options:
40 --archive: messages from these sources will not appear in the inbox
41 --unusual: these sources will not be polled when the flag --the-usual
44 Regardless of whether the sources are new or not, they will be polled,
45 and any new messages will be added to the index, as parameterized by
46 the following options:
47 --force-archive: regardless of the source "archive" flag, any new
48 messages found will not appear in the inbox.
49 --force-read: any messages found will not be marked as new.
51 The following options can also be specified:
52 --the-usual: import new messages from all usual sources
53 --rebuild: rebuild the index for the specified sources rather than
54 just adding new messages. Useful if the sources
55 have changed in any way *other* than new messages
57 --force-rebuild: force a rebuild of all messages in the inbox, not just
58 ones that have changed. You probably won't need this
59 unless William changes the index format.
60 --optimize: optimize the index after adding any new messages.
61 --help: don't do anything, just show this message.
66 ## for sources that require login information, prompt the user for
67 ## that. also provide a list of previously-defined login info to
68 ## choose from, if any.
69 def get_login_info uri, sources
71 accounts = sources.map do |s|
72 next unless s.respond_to?(:username)
74 [suri.host, s.username, s.password]
75 end.compact.uniq.sort_by { |h, u, p| h == uri.host ? 0 : 1 }
77 username, password = nil, nil
78 unless accounts.empty?
79 say "Would you like to use the same account as for a previous source?"
81 accounts.each do |host, olduser, oldpw|
82 menu.choice("Use the account info for #{olduser}@#{host}") { username, password = olduser, oldpw }
84 menu.choice("Use a new account") { }
88 unless username && password
89 username = ask("Username for #{uri.host}: ");
90 password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
98 educate_user if ARGV.member? '--help'
100 archive = ARGV.delete "--archive"
101 unusual = ARGV.delete "--unusual"
102 force_archive = ARGV.delete "--force-archive"
103 force_read = ARGV.delete "--force-read"
104 the_usual = ARGV.delete "--the-usual"
105 rebuild = ARGV.delete "--rebuild"
106 force_rebuild = ARGV.delete "--force-rebuild"
107 optimize = ARGV.delete "--optimize"
108 start_at = # ok really need to use optparse or something now
109 if(i = ARGV.index("--start-at"))
110 raise "start-at requires a numeric argument: #{ARGV[i + 1].inspect}" unless ARGV.length > (i + 1) && ARGV[i + 1] =~ /\d/
112 ARGV.delete_at(i).to_i # whoa!
115 if(o = ARGV.find { |x| x =~ /^--/ })
116 $stderr.puts "error: unknown option #{o}"
120 $terminal.wrap_at = :auto
122 index = Redwood::Index.new
125 sources = ARGV.map do |uri|
126 uri = "mbox://#{uri}" unless uri =~ %r!://!
127 source = index.source_for uri
131 when %r!^mbox\+ssh://!
132 say "For SSH connections, if you will use public key authentication, you may leave the username and password blank."
134 username, password = get_login_info uri, index.sources
135 Redwood::MBox::SSHLoader.new(uri, username, password, nil, !unusual, !!archive)
137 username, password = get_login_info uri, index.sources
138 Redwood::IMAP.new(uri, username, password, nil, !unusual, !!archive)
140 Redwood::MBox::Loader.new(uri, nil, !unusual, !!archive)
142 index.add_source source
147 sources = (sources + index.usual_sources).uniq if the_usual
148 if rebuild || force_rebuild
150 sources.each { |s| s.seek_to! start_at }
152 sources.each { |s| s.reset! }
159 sources.each do |source|
161 $stderr.puts "error loading messages from #{source}: #{source.broken_msg}"
165 puts "loading from #{source}... "
168 source.each do |offset, labels|
169 start_offset ||= offset
170 labels -= [:inbox] if force_archive
171 labels -= [:unread] if force_read
173 m = Redwood::Message.new :source => source, :source_info => offset, :labels => labels
175 puts "skipping duplicate message #{m.id}"
181 m.remove_label :unread if m.status == "RO" unless force_read
182 puts "# message at #{offset}, labels: #{labels * ', '}"
183 if (rebuild || force_rebuild) &&
184 (docid, entry = index.load_entry_for_id(m.id)) && entry
185 if force_rebuild || entry[:source_info].to_i != offset
186 puts "replacing message #{m.id} labels #{entry[:label].inspect} (offset #{entry[:source_info]} => #{offset})"
187 m.labels = entry[:label].split.map { |l| l.intern }
188 num += 1 if index.update_message m, source, offset
191 num += 1 if index.add_message m
193 rescue Redwood::MessageFormatError, Redwood::SourceError => e
194 $stderr.puts "ignoring erroneous message at #{source}##{offset}: #{e.message}"
196 if num % 1000 == 0 && num > 0
197 elapsed = Time.now - start
198 pctdone = (offset.to_f - start_offset) / (source.total.to_f - start_offset)
199 remaining = (source.total.to_f - offset.to_f) * (elapsed.to_f / (offset.to_f - start_offset))
200 puts "## #{num} (#{(pctdone * 100.0)}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining"
203 puts "loaded #{num} messages" unless num == 0
206 $stderr.puts "saving index and sources..."
211 if rebuild || force_rebuild
212 puts "deleting missing messages from the index..."
214 sources.each do |source|
215 raise "no source id for #{source}" unless source.id
216 q = "+source_id:#{source.id}"
217 q += " +source_info: >= #{start_at}" if start_at
219 num += index.index.search_each(q, :limit => :all) do |docid, score|
220 mid = index.index[docid][:message_id]
222 puts "deleting #{mid}"
223 index.index.delete docid
228 puts "deleted #{numdel} / #{num} messages"
232 puts "optimizing index..."
233 optt = time { index.index.optimize }
234 puts "optimized index of size #{index.size} in #{optt}s."