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") { }
85 menu.prompt = "Account selection? "
89 unless username && password
90 username = ask("Username for #{uri.host}: ");
91 password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
99 educate_user if ARGV.member? '--help'
101 archive = ARGV.delete "--archive"
102 unusual = ARGV.delete "--unusual"
103 force_archive = ARGV.delete "--force-archive"
104 force_read = ARGV.delete "--force-read"
105 the_usual = ARGV.delete "--the-usual"
106 rebuild = ARGV.delete "--rebuild"
107 force_rebuild = ARGV.delete "--force-rebuild"
108 optimize = ARGV.delete "--optimize"
109 start_at = # ok really need to use optparse or something now
110 if(i = ARGV.index("--start-at"))
111 raise "start-at requires a numeric argument: #{ARGV[i + 1].inspect}" unless ARGV.length > (i + 1) && ARGV[i + 1] =~ /\d/
113 ARGV.delete_at(i).to_i # whoa!
116 if(o = ARGV.find { |x| x =~ /^--/ })
117 $stderr.puts "error: unknown option #{o}"
121 $terminal.wrap_at = :auto
123 index = Redwood::Index.new
126 sources = ARGV.map do |uri|
127 uri = "mbox://#{uri}" unless uri =~ %r!://!
128 source = index.source_for uri
132 when %r!^mbox\+ssh://!
133 say "For SSH connections, if you will use public key authentication, you may leave the username and password blank."
135 username, password = get_login_info uri, index.sources
136 Redwood::MBox::SSHLoader.new(uri, username, password, nil, !unusual, !!archive)
138 username, password = get_login_info uri, index.sources
139 Redwood::IMAP.new(uri, username, password, nil, !unusual, !!archive)
141 Redwood::MBox::Loader.new(uri, nil, !unusual, !!archive)
143 index.add_source source
148 sources = (sources + index.usual_sources).uniq if the_usual
149 if rebuild || force_rebuild
151 sources.each { |s| s.seek_to! start_at }
153 sources.each { |s| s.reset! }
160 sources.each do |source|
162 $stderr.puts "error loading messages from #{source}: #{source.broken_msg}"
166 puts "loading from #{source}... "
169 source.each do |offset, labels|
170 start_offset ||= offset
171 labels -= [:inbox] if force_archive
172 labels -= [:unread] if force_read
174 m = Redwood::Message.new :source => source, :source_info => offset, :labels => labels
176 puts "skipping duplicate message #{m.id}"
182 m.remove_label :unread if m.status == "RO" unless force_read
183 puts "# message at #{offset}, labels: #{labels * ', '}"
184 if (rebuild || force_rebuild) &&
185 (docid, entry = index.load_entry_for_id(m.id)) && entry
186 if force_rebuild || entry[:source_info].to_i != offset
187 puts "replacing message #{m.id} labels #{entry[:label].inspect} (offset #{entry[:source_info]} => #{offset})"
188 m.labels = entry[:label].split.map { |l| l.intern }
189 num += 1 if index.update_message m, source, offset
192 num += 1 if index.add_message m
194 rescue Redwood::MessageFormatError, Redwood::SourceError => e
195 $stderr.puts "ignoring erroneous message at #{source}##{offset}: #{e.message}"
197 if num % 1000 == 0 && num > 0
198 elapsed = Time.now - start
199 pctdone = (offset.to_f - start_offset) / (source.total.to_f - start_offset)
200 remaining = (source.total.to_f - offset.to_f) * (elapsed.to_f / (offset.to_f - start_offset))
201 puts "## #{num} (#{(pctdone * 100.0)}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining"
204 puts "loaded #{num} messages" unless num == 0
207 $stderr.puts "saving index and sources..."
212 if rebuild || force_rebuild
213 puts "deleting missing messages from the index..."
215 sources.each do |source|
216 raise "no source id for #{source}" unless source.id
217 q = "+source_id:#{source.id}"
218 q += " +source_info: >= #{start_at}" if start_at
220 num += index.index.search_each(q, :limit => :all) do |docid, score|
221 mid = index.index[docid][:message_id]
223 puts "deleting #{mid}"
224 index.index.delete docid
229 puts "deleted #{numdel} / #{num} messages"
233 puts "optimizing index..."
234 optt = time { index.index.optimize }
235 puts "optimized index of size #{index.size} in #{optt}s."