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 +50 -0
- data/History.txt +12 -0
- data/Manifest.txt +2 -0
- data/Rakefile +5 -1
- data/bin/sup +24 -8
- data/bin/sup-add +106 -0
- data/bin/sup-import +85 -165
- data/doc/FAQ.txt +48 -11
- data/doc/TODO +45 -12
- data/doc/UserGuide.txt +54 -42
- data/lib/sup.rb +6 -2
- data/lib/sup/buffer.rb +20 -4
- data/lib/sup/contact.rb +22 -12
- data/lib/sup/draft.rb +27 -7
- data/lib/sup/imap.rb +107 -97
- data/lib/sup/index.rb +35 -25
- data/lib/sup/label.rb +2 -2
- data/lib/sup/mbox.rb +20 -15
- data/lib/sup/mbox/loader.rb +0 -1
- data/lib/sup/mbox/ssh-file.rb +58 -47
- data/lib/sup/mbox/ssh-loader.rb +13 -20
- data/lib/sup/message.rb +15 -9
- data/lib/sup/mode.rb +16 -6
- data/lib/sup/modes/compose-mode.rb +7 -3
- data/lib/sup/modes/contact-list-mode.rb +50 -25
- data/lib/sup/modes/edit-message-mode.rb +11 -8
- data/lib/sup/modes/inbox-mode.rb +19 -3
- data/lib/sup/modes/label-list-mode.rb +4 -12
- data/lib/sup/modes/log-mode.rb +6 -0
- data/lib/sup/modes/reply-mode.rb +19 -6
- data/lib/sup/modes/resume-mode.rb +13 -9
- data/lib/sup/modes/scroll-mode.rb +13 -2
- data/lib/sup/modes/thread-index-mode.rb +94 -43
- data/lib/sup/modes/thread-view-mode.rb +198 -130
- data/lib/sup/person.rb +1 -1
- data/lib/sup/poll.rb +60 -47
- data/lib/sup/sent.rb +3 -2
- data/lib/sup/source.rb +14 -6
- data/lib/sup/textfield.rb +1 -0
- data/lib/sup/thread.rb +76 -23
- data/lib/sup/update.rb +2 -3
- data/lib/sup/util.rb +13 -13
- metadata +14 -2
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
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", '
|
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
|
-
|
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.
|
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.
|
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 '
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
101
|
-
|
102
|
-
archive
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
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 =
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
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 "
|
121
|
+
puts "Added #{num_added}, updated #{num_updated} messages from #{source}."
|
208
122
|
end
|
209
123
|
ensure
|
210
|
-
|
124
|
+
puts "Saving index and sources..."
|
211
125
|
index.save
|
212
126
|
Redwood::finish
|
213
127
|
end
|
214
128
|
|
215
|
-
|
216
|
-
|
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 "
|
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 "
|
152
|
+
puts "Deleted #{numdel} / #{num} messages"
|
233
153
|
end
|
234
154
|
|
235
|
-
if optimize
|
236
|
-
puts "
|
155
|
+
if opts[:optimize]
|
156
|
+
puts "Optimizing index..."
|
237
157
|
optt = time { index.index.optimize }
|
238
|
-
puts "
|
158
|
+
puts "Optimized index of size #{index.size} in #{optt}s."
|
239
159
|
end
|