sup 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sup might be problematic. Click here for more details.

data/HACKING ADDED
@@ -0,0 +1,50 @@
1
+ Running Sup locally
2
+ -------------------
3
+ Invoke it like this:
4
+
5
+ ruby -I lib -w bin/sup
6
+
7
+ Coding standards
8
+ ----------------
9
+
10
+ - Don't wrap code unless it really benefits from it. The days of
11
+ 80-column displays are long over. But do wrap comments and other
12
+ text at whatever Emacs meta-Q does.
13
+ - Use as few parentheses as possible.
14
+ - Use {} for one-liner blocks and do/end for multi-line blocks.
15
+
16
+ How messages are updated in the index
17
+ -------------------------------------
18
+
19
+ Ferret doesn't have any concept of updating; to change message state
20
+ it must be deleted then re-added to the index.
21
+
22
+ Thus there are a couple situations where we'll have a message to be
23
+ "added", but it already exists in the index, and we need to decide
24
+ which parts of which version to keep:
25
+
26
+ 1. The user has changed the state of the message, e.g. read it or
27
+ added a user label. In this case we want to use the state of the
28
+ version in memory, but keep everything else on disk.
29
+
30
+ This is the behavior of Index#update_message
31
+
32
+ 2. We've received a new copy of the message. Crucially, this can
33
+ happen for two different reasons:
34
+
35
+ a. The message was sent to a mailing list to which the user is
36
+ subscribed, and we're now getting that message back, possibly
37
+ with altered content (subject mangling, signature adding, etc.)
38
+
39
+ b. The user has moved the message between sources. E.g. if the
40
+ primary inbox has a quota, and other sources are on local,
41
+ quota-less disk, the user may regularly move messages from the
42
+ inbox to the sources on disk.
43
+
44
+ In both of these cases, the solution is to keep the state from the
45
+ index, but use the new message contents.
46
+
47
+ This is the behavior of Index#update_or_add_message, which can be
48
+ also be called for new message.
49
+
50
+
data/History.txt CHANGED
@@ -1,3 +1,15 @@
1
+ == 0.0.7 / 2007-02-12
2
+ * Split sup-import into two bits: sup-import and sup-add.
3
+ * Command-line arguments now handled by trollop.
4
+ * Better error handling for IMAP and svn+ssh.
5
+ * Messages can now be moved between sources while preserving all
6
+ message state.
7
+ * New commands in thread-view-mode:
8
+ - 'a' to add an email to the addressbook
9
+ - 'S' to search for all email to/from an email address
10
+ - 'A' to kill buffer and archive thread in one swell foop
11
+ * Removed hoe dependency.
12
+
1
13
  == 0.0.6 / 2007-01-06
2
14
  * Very minor fix to support more types of IMAP authentication.
3
15
 
data/Manifest.txt CHANGED
@@ -1,9 +1,11 @@
1
+ HACKING
1
2
  History.txt
2
3
  LICENSE
3
4
  Manifest.txt
4
5
  README.txt
5
6
  Rakefile
6
7
  bin/sup
8
+ bin/sup-add
7
9
  bin/sup-import
8
10
  bin/sup-recover-sources
9
11
  doc/FAQ.txt
data/Rakefile CHANGED
@@ -4,6 +4,10 @@ require 'rubygems'
4
4
  require 'hoe'
5
5
  require './lib/sup.rb'
6
6
 
7
+ class Hoe
8
+ def extra_deps; @extra_deps.reject { |x| Array(x).first == "hoe" } end
9
+ end # thanks to "Mike H"
10
+
7
11
  Hoe.new('sup', Redwood::VERSION) do |p|
8
12
  p.rubyforge_name = 'sup'
9
13
  p.author = "William Morgan"
@@ -12,7 +16,7 @@ Hoe.new('sup', Redwood::VERSION) do |p|
12
16
  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2].gsub(/^\s+/, "")
13
17
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
18
  p.email = "wmorgan-sup@masanjin.net"
15
- p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh']
19
+ p.extra_deps = [['ferret', '>= 0.10.13'], ['ncurses', '>= 0.9.1'], ['rmail', '>= 0.17'], 'highline', 'net-ssh', 'trollop']
16
20
  end
17
21
 
18
22
  rule 'ss?.png' => 'ss?-small.png' do |t|
data/bin/sup CHANGED
@@ -12,9 +12,9 @@ global_keymap = Keymap.new do |k|
12
12
  k.add :quit, "Quit Redwood", 'q'
13
13
  k.add :help, "Show help", 'H', '?'
14
14
  k.add :roll_buffers, "Switch to next buffer", 'b'
15
- k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
15
+ # k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
16
16
  k.add :kill_buffer, "Kill the current buffer", 'x'
17
- k.add :list_buffers, "List all buffers", 'A'
17
+ k.add :list_buffers, "List all buffers", 'B'
18
18
  k.add :list_contacts, "List contacts", 'C'
19
19
  k.add :redraw, "Redraw screen", :ctrl_l
20
20
  k.add :search, "Search messages", '/'
@@ -71,6 +71,8 @@ begin
71
71
  c.add :twiddle_color, Ncurses::COLOR_BLUE, Ncurses::COLOR_BLACK
72
72
  c.add :label_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
73
73
  c.add :message_patina_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_GREEN
74
+ c.add :alternate_patina_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_BLUE
75
+ c.add :missing_message_color, Ncurses::COLOR_BLACK, Ncurses::COLOR_RED
74
76
  c.add :mime_color, Ncurses::COLOR_CYAN, Ncurses::COLOR_BLACK
75
77
  c.add :quote_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
76
78
  c.add :sig_patina_color, Ncurses::COLOR_YELLOW, Ncurses::COLOR_BLACK
@@ -100,7 +102,17 @@ begin
100
102
  Logger.make_buf
101
103
 
102
104
  bm.draw_screen
103
- imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread { sleep 1; PollManager.poll } }
105
+ Index.usual_sources.each do |s|
106
+ reporting_thread do
107
+ begin
108
+ s.connect
109
+ rescue SourceError => e
110
+ Redwood::log "Fatal error loading from #{s}: #{e.message}"
111
+ end
112
+ end if s.respond_to? :connect
113
+ end
114
+
115
+ imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread { sleep 1; PollManager.poll } }
104
116
 
105
117
  PollManager.start_thread
106
118
 
@@ -116,7 +128,7 @@ begin
116
128
  x = global_keymap.action_for c
117
129
  case x
118
130
  when :quit
119
- break
131
+ break if bm.kill_all_buffers_safely
120
132
  when :help
121
133
  curmode = bm.focus_buf.mode
122
134
  bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
@@ -125,12 +137,12 @@ begin
125
137
  when :roll_buffers_backwards
126
138
  bm.roll_buffers_backwards
127
139
  when :kill_buffer
128
- bm.kill_buffer bm.focus_buf if bm.focus_buf.mode.killable?
140
+ bm.kill_buffer_safely bm.focus_buf
129
141
  when :list_buffers
130
142
  bm.spawn_unless_exists("Buffer List") { BufferListMode.new }
131
143
  when :list_contacts
132
144
  b = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
133
- b.mode.load_more b.content_height
145
+ b.mode.load_in_background
134
146
  when :search
135
147
  text = bm.ask :search, "query: "
136
148
  next unless text && text !~ /^\s*$/
@@ -145,7 +157,6 @@ begin
145
157
  rescue Ferret::QueryParser::QueryParseException => e
146
158
  bm.flash "Couldn't parse query."
147
159
  end
148
-
149
160
  when :list_labels
150
161
  b = bm.spawn_unless_exists("Label List") { LabelListMode.new }
151
162
  b.mode.load_in_background
@@ -181,12 +192,17 @@ begin
181
192
  end
182
193
  end
183
194
  end
184
- bm.kill_all_buffers
185
195
  rescue Exception => e
186
196
  $exception ||= e
187
197
  ensure
188
198
  Redwood::finish
189
199
  stop_cursing
200
+
201
+ # don't ask me why, but sometimes it's necessary to print something
202
+ # to stderr at this point or the exception doesn't get printed.
203
+ # doesn't get printed. WHY?
204
+
205
+ $stderr.puts " "
190
206
  end
191
207
 
192
208
  Index.save unless $exception # TODO: think about this
data/bin/sup-add ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'uri'
4
+ require 'rubygems'
5
+ require 'highline/import'
6
+ require 'trollop'
7
+ require "sup"
8
+
9
+ Thread.abort_on_exception = true # make debugging possible
10
+
11
+ $opts = Trollop::options do
12
+ version "sup-add (sup #{Redwood::VERSION})"
13
+ banner <<EOS
14
+ Adds a source to the Sup source list.
15
+
16
+ Usage:
17
+ sup-add [options] <source>+
18
+
19
+ where <source>+ is one or more sources.
20
+
21
+ For mbox files on local disk, use the form:
22
+ mbox://<path to mbox file>
23
+ or simply
24
+ <path to mbox file>
25
+
26
+ For mbox files on remote machines, use the form:
27
+ mbox+ssh://<machine name>/<path to mbox file>
28
+
29
+ For IMAP folders, use the form (note no username or password!):
30
+ imap://<machine name>/ # unsecure, "INBOX" folder
31
+ imap://<machine name>/<folder> # unsecure, arbitrary
32
+ imaps://<machine name>/ # secure, "INBOX" folder
33
+ imaps://<machine name>/<folder> # secure, arbitrary folder
34
+
35
+ Options are:
36
+ EOS
37
+ opt :archive, "Automatically archive all new messages from these sources."
38
+ opt :unusual, "Do not automatically poll these sources for new messages."
39
+ opt :force_new, "Create a new account for this source, even if one already exists."
40
+ end
41
+
42
+ Trollop::die "require one or more sources" if ARGV.empty?
43
+
44
+ ## for sources that require login information, prompt the user for
45
+ ## that. also provide a list of previously-defined login info to
46
+ ## choose from, if any.
47
+ def get_login_info uri, sources
48
+ uri = URI(uri)
49
+ accounts = sources.map do |s|
50
+ next unless s.respond_to?(:username)
51
+ suri = URI(s.uri)
52
+ [suri.host, s.username, s.password]
53
+ end.compact.uniq.sort_by { |h, u, p| h == uri.host ? 0 : 1 }
54
+
55
+ username, password = nil, nil
56
+ unless accounts.empty? || $opts[:force_new]
57
+ say "Would you like to use the same account as for a previous source for #{uri}?"
58
+ choose do |menu|
59
+ accounts.each do |host, olduser, oldpw|
60
+ menu.choice("Use the account info for #{olduser}@#{host}") { username, password = olduser, oldpw }
61
+ end
62
+ menu.choice("Use a new account") { }
63
+ menu.prompt = "Account selection? "
64
+ end
65
+ end
66
+
67
+ unless username && password
68
+ username = ask("Username for #{uri.host}: ");
69
+ password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
70
+ puts # why?
71
+ end
72
+
73
+ [username, password]
74
+ end
75
+
76
+ $terminal.wrap_at = :auto
77
+ Redwood::start
78
+ index = Redwood::Index.new
79
+ index.load
80
+
81
+ ARGV.each do |uri|
82
+ uri = "mbox://#{uri}" unless uri =~ %r!://!
83
+ if !$opts[:force_new] && index.source_for(uri)
84
+ say "Already know about #{uri}; skipping."
85
+ next
86
+ end
87
+ source =
88
+ case uri
89
+ when %r!^mbox\+ssh://!
90
+ say "For SSH connections, if you will use public key authentication, you may leave the username and password blank."
91
+ say ""
92
+ username, password = get_login_info uri, index.sources
93
+ Redwood::MBox::SSHLoader.new(uri, username, password, nil, !$opts[:unusual], $opts[:archive])
94
+ when %r!^imaps?://!
95
+ username, password = get_login_info uri, index.sources
96
+ Redwood::IMAP.new(uri, username, password, nil, !$opts[:unusual], $opts[:archive])
97
+ else
98
+ Redwood::MBox::Loader.new(uri, nil, !$opts[:unusual], $opts[:archive])
99
+ end
100
+ say "Adding #{source}..."
101
+ index.add_source source
102
+ end
103
+
104
+ say "Saving source list..."
105
+ index.save
106
+ Redwood::finish
data/bin/sup-import CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  require 'uri'
4
4
  require 'rubygems'
5
- require 'highline/import'
5
+ require 'trollop'
6
6
  require "sup"
7
7
 
8
-
9
8
  Thread.abort_on_exception = true # make debugging possible
10
9
 
11
10
  class Float
@@ -25,215 +24,136 @@ def time
25
24
  Time.now - startt
26
25
  end
27
26
 
28
- def educate_user
29
- $stderr.puts <<EOS
30
- Loads messages into the Sup index, adding sources as needed to the
31
- source list.
27
+ opts = Trollop::options do
28
+ version "sup-import (sup #{Redwood::VERSION})"
29
+ banner <<EOS
30
+ Imports messages into the Sup index from one or more sources.
32
31
 
33
32
  Usage:
34
33
  sup-import [options] <source>*
35
- where <source>* is zero or more source descriptions (e.g., mbox
36
- filenames on disk, or imap/imaps URIs).
37
-
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
42
- is called
43
-
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.
50
-
51
- The following options can also be specified:
52
- --verbose: print message ids as they're processed
53
- --the-usual: import new messages from all usual sources
54
- --rebuild: rebuild the index for the specified sources rather than
55
- just adding new messages. Useful if the sources
56
- have changed in any way *other* than new messages
57
- being added.
58
- --force-rebuild: force a rebuild of all messages in the inbox, not just
59
- ones that have changed. You probably won't need this
60
- unless William changes the index format.
61
- --optimize: optimize the index after adding any new messages.
62
- --help: don't do anything, just show this message.
63
- EOS
64
- exit
65
- end
66
- #' stupid ruby-mode
67
-
68
- ## for sources that require login information, prompt the user for
69
- ## that. also provide a list of previously-defined login info to
70
- ## choose from, if any.
71
- def get_login_info uri, sources
72
- uri = URI(uri)
73
- accounts = sources.map do |s|
74
- next unless s.respond_to?(:username)
75
- suri = URI(s.uri)
76
- [suri.host, s.username, s.password]
77
- end.compact.uniq.sort_by { |h, u, p| h == uri.host ? 0 : 1 }
78
-
79
- username, password = nil, nil
80
- unless accounts.empty?
81
- say "Would you like to use the same account as for a previous source for #{uri}?"
82
- choose do |menu|
83
- accounts.each do |host, olduser, oldpw|
84
- menu.choice("Use the account info for #{olduser}@#{host}") { username, password = olduser, oldpw }
85
- end
86
- menu.choice("Use a new account") { }
87
- menu.prompt = "Account selection? "
88
- end
89
- end
90
34
 
91
- unless username && password
92
- username = ask("Username for #{uri.host}: ");
93
- password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
94
- puts # why?
95
- end
96
-
97
- [username, password]
98
- end
35
+ where <source>* is zero or more source URIs or mbox filenames. If no
36
+ sources are given, imports messages from all sources marked as
37
+ "usual".
99
38
 
100
- educate_user if ARGV.member? '--help'
101
-
102
- archive = ARGV.delete "--archive"
103
- unusual = ARGV.delete "--unusual"
104
- force_archive = ARGV.delete "--force-archive"
105
- force_read = ARGV.delete "--force-read"
106
- the_usual = ARGV.delete "--the-usual"
107
- rebuild = ARGV.delete "--rebuild"
108
- force_rebuild = ARGV.delete "--force-rebuild"
109
- optimize = ARGV.delete "--optimize"
110
- verbose = ARGV.delete "--verbose"
111
- start_at = # ok really need to use optparse or something now
112
- if(i = ARGV.index("--start-at"))
113
- raise "start-at requires a numeric argument: #{ARGV[i + 1].inspect}" unless ARGV.length > (i + 1) && ARGV[i + 1] =~ /\d/
114
- ARGV.delete_at i
115
- ARGV.delete_at(i).to_i # whoa!
116
- end
117
-
118
- if(o = ARGV.find { |x| x =~ /^--/ })
119
- $stderr.puts "error: unknown option #{o}"
120
- educate_user
39
+ Options are:
40
+ EOS
41
+ opt :archive, "Automatically archive any imported messages."
42
+ opt :read, "Automatically mark as read any imported messages."
43
+ opt :verbose, "Print message ids as they're processed."
44
+ opt :optimize, "As the last stage of the import, optimize the index."
45
+ text <<EOS
46
+
47
+ The following options allow sup-import to consider *all* messages in the
48
+ source, not just new ones:
49
+ EOS
50
+ opt :rebuild, "Scan over the entire source and update the index to account for any messages that have been deleted, altered, or moved from another source."
51
+ opt :full_rebuild, "Re-insert all messages in the source, not just ones that have changed or are new."
52
+ opt :start_at, "For rescan and rebuild, start at the given offset.", :type => :int
53
+ opt :overwrite_state, "For --full-rebuild, overwrite the message state to the default state for that source, obeying --archive and --read if given."
121
54
  end
55
+ Trollop::die :start_at, "must be non-negative" if (opts[:start_at] || 0) < 0
56
+ Trollop::die :start_at, "requires either --rebuild or --full-rebuild" if opts[:start_at] && !(opts[:rebuild] || opts[:full_rebuild])
57
+ Trollop::die :overwrite_state, "requires --full-rebuild" if opts[:overwrite_state] && !opts[:full_rebuild]
58
+ Trollop::die :force_rebuild, "cannot be specified with --rebuild" if opts[:full_rebuild] && opts[:rebuild]
122
59
 
123
- $terminal.wrap_at = :auto
124
60
  Redwood::start
125
61
  index = Redwood::Index.new
126
62
  index.load
127
63
 
128
64
  sources = ARGV.map do |uri|
129
65
  uri = "mbox://#{uri}" unless uri =~ %r!://!
130
- source = index.source_for uri
131
- unless source
132
- source =
133
- case uri
134
- when %r!^mbox\+ssh://!
135
- say "For SSH connections, if you will use public key authentication, you may leave the username and password blank."
136
- say "\n"
137
- username, password = get_login_info uri, index.sources
138
- Redwood::MBox::SSHLoader.new(uri, username, password, nil, !unusual, !!archive)
139
- when %r!^imaps?://!
140
- username, password = get_login_info uri, index.sources
141
- Redwood::IMAP.new(uri, username, password, nil, !unusual, !!archive)
142
- else
143
- Redwood::MBox::Loader.new(uri, nil, !unusual, !!archive)
144
- end
145
- index.add_source source
146
- end
147
- source
66
+ index.source_for uri or raise "Unknown source: #{uri}"
148
67
  end
149
68
 
150
- sources = (sources + index.usual_sources).uniq if the_usual
151
- if rebuild || force_rebuild
152
- if start_at
153
- sources.each { |s| s.seek_to! start_at }
69
+ sources = index.usual_sources if sources.empty?
70
+
71
+ if opts[:rebuild] || opts[:full_rebuild]
72
+ if opts[:start_at]
73
+ sources.each { |s| s.seek_to! opts[:start_at] }
154
74
  else
155
75
  sources.each { |s| s.reset! }
156
76
  end
157
77
  end
158
78
 
79
+ last_update = start = Time.now
159
80
  found = {}
160
- start = Time.now
161
81
  begin
162
82
  sources.each do |source|
163
- if source.broken?
164
- $stderr.puts "error loading messages from #{source}: #{source.broken_msg}"
165
- next
166
- end
167
- next if source.done?
168
- puts "loading from #{source}... "
169
- num = 0
170
- start_offset = nil
171
- source.each do |offset, labels|
172
- start_offset ||= offset
173
- labels -= [:inbox] if force_archive || archive
174
- labels -= [:unread] if force_read
175
- begin
176
- m = Redwood::Message.new :source => source, :source_info => offset, :labels => labels
177
- if found[m.id]
178
- puts "skipping duplicate message #{m.id}"
179
- next
180
- else
181
- found[m.id] = true
182
- end
183
-
184
- m.remove_label :unread if m.source_marked_read? unless force_read
185
- puts "# message at #{offset}, labels: #{labels * ', '}" if verbose unless rebuild
186
- labels.each { |l| Redwood::LabelManager << l }
187
- if (rebuild || force_rebuild) &&
188
- (docid, entry = index.load_entry_for_id(m.id)) && entry
189
- if force_rebuild || entry[:source_info].to_i != offset
190
- puts "replacing message #{m.id} labels #{entry[:label].inspect} (offset #{entry[:source_info]} => #{offset})"
191
- m.labels = entry[:label].split.map { |l| l.intern }
192
- num += 1 if index.update_message m, source, offset
193
- end
194
- else
195
- num += 1 if index.add_message m
196
- end
197
- rescue Redwood::MessageFormatError, Redwood::SourceError => e
198
- $stderr.puts "ignoring erroneous message at #{source}##{offset}: #{e.message}"
83
+ num_added = 0
84
+ num_updated = 0
85
+ puts "Scanning #{source}..."
86
+ Redwood::PollManager.add_new_messages_from source do |m, offset, entry|
87
+ ## if the entry exists on disk
88
+ if entry && !opts[:overwrite_state]
89
+ m.labels = entry[:label].split(/\s+/).map { |x| x.intern }
90
+ else
91
+ ## m.labels defaults to labels from the source
92
+ m.labels -= [:inbox] if opts[:archive]
93
+ m.labels -= [:unread] if opts[:read]
199
94
  end
200
- if num % 1000 == 0 && num > 0
201
- elapsed = Time.now - start
202
- pctdone = source.pct_done
95
+
96
+ if Time.now - last_update > 60
97
+ last_update = Time.now
98
+ elapsed = last_update - start
99
+ pctdone = source.respond_to?(:pct_done) ? source.pct_done : 100.0 * (source.cur_offset.to_f - source.start_offset).to_f / (source.end_offset - source.start_offset).to_f
203
100
  remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
204
101
  puts "## #{num} (#{pctdone}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining"
205
102
  end
103
+
104
+ ## update if...
105
+ if entry.nil? # it's a new message; or
106
+ puts "Adding message at #{offset}, labels: #{m.labels * ' '}" if opts[:verbose]
107
+ num_added += 1
108
+ found[m.id] = true
109
+ m
110
+ elsif opts[:full_rebuild] || # we're updating everyone; or
111
+ (opts[:rebuild] && (entry[:source_id].to_i != source.id || entry[:source_info].to_i != offset)) # we're updating just the changed ones
112
+ puts "Updating message at #{offset} (from #{m.from.longname}, subject '#{m.subj}'), source #{entry[:source_id]} => #{source.id}, offset #{entry[:source_info]} => #{offset}, labels: {#{m.labels * ', '}}" if opts[:verbose]
113
+ num_updated += 1 unless found[m.id]
114
+ found[m.id] = true
115
+ m
116
+ else
117
+ found[m.id] = true
118
+ nil
119
+ end
206
120
  end
207
- puts "loaded #{num} messages" unless num == 0
121
+ puts "Added #{num_added}, updated #{num_updated} messages from #{source}."
208
122
  end
209
123
  ensure
210
- $stderr.puts "saving index and sources..."
124
+ puts "Saving index and sources..."
211
125
  index.save
212
126
  Redwood::finish
213
127
  end
214
128
 
215
- if rebuild || force_rebuild
216
- puts "deleting missing messages from the index..."
129
+ ## delete any messages in the index that claim they're from one of
130
+ ## these sources, but that we didn't see.
131
+ ##
132
+ ## kinda crappy code here, because we delve directly into the Ferret
133
+ ## API.
134
+ ##
135
+ ## TODO: move this to Index, i suppose.
136
+ if opts[:rebuild] || opts[:full_rebuild]
137
+ puts "Deleting missing messages from the index..."
217
138
  numdel = num = 0
218
139
  sources.each do |source|
219
140
  raise "no source id for #{source}" unless source.id
220
141
  q = "+source_id:#{source.id}"
221
- q += " +source_info: >= #{start_at}" if start_at
222
- #p q
142
+ q += " +source_info: >= #{opts[:start_at]}" if opts[:start_at]
223
143
  num += index.index.search_each(q, :limit => :all) do |docid, score|
224
144
  mid = index.index[docid][:message_id]
145
+ # puts "got #{mid}"
225
146
  next if found[mid]
226
- puts "deleting #{mid}"
147
+ puts "Deleting #{mid}" if opts[:verbose]
227
148
  index.index.delete docid
228
149
  numdel += 1
229
150
  end
230
- #p num
231
151
  end
232
- puts "deleted #{numdel} / #{num} messages"
152
+ puts "Deleted #{numdel} / #{num} messages"
233
153
  end
234
154
 
235
- if optimize
236
- puts "optimizing index..."
155
+ if opts[:optimize]
156
+ puts "Optimizing index..."
237
157
  optt = time { index.index.optimize }
238
- puts "optimized index of size #{index.size} in #{optt}s."
158
+ puts "Optimized index of size #{index.size} in #{optt}s."
239
159
  end