sup 0.0.8 → 0.1
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 +6 -36
- data/History.txt +11 -0
- data/Manifest.txt +5 -0
- data/README.txt +13 -31
- data/Rakefile +3 -3
- data/bin/sup +167 -89
- data/bin/sup-add +39 -29
- data/bin/sup-config +57 -31
- data/bin/sup-sync +60 -54
- data/bin/sup-sync-back +143 -0
- data/doc/FAQ.txt +56 -19
- data/doc/Philosophy.txt +34 -33
- data/doc/TODO +76 -46
- data/doc/UserGuide.txt +142 -122
- data/lib/sup.rb +76 -36
- data/lib/sup/account.rb +27 -19
- data/lib/sup/buffer.rb +130 -44
- data/lib/sup/contact.rb +1 -1
- data/lib/sup/draft.rb +1 -2
- data/lib/sup/imap.rb +64 -19
- data/lib/sup/index.rb +95 -16
- data/lib/sup/keymap.rb +1 -1
- data/lib/sup/label.rb +31 -5
- data/lib/sup/maildir.rb +7 -5
- data/lib/sup/mbox.rb +34 -15
- data/lib/sup/mbox/loader.rb +30 -12
- data/lib/sup/mbox/ssh-loader.rb +7 -5
- data/lib/sup/message.rb +93 -44
- data/lib/sup/modes/buffer-list-mode.rb +1 -1
- data/lib/sup/modes/completion-mode.rb +55 -0
- data/lib/sup/modes/compose-mode.rb +6 -25
- data/lib/sup/modes/contact-list-mode.rb +1 -1
- data/lib/sup/modes/edit-message-mode.rb +119 -29
- data/lib/sup/modes/file-browser-mode.rb +108 -0
- data/lib/sup/modes/forward-mode.rb +3 -20
- data/lib/sup/modes/inbox-mode.rb +9 -12
- data/lib/sup/modes/label-list-mode.rb +28 -46
- data/lib/sup/modes/label-search-results-mode.rb +1 -16
- data/lib/sup/modes/line-cursor-mode.rb +44 -5
- data/lib/sup/modes/person-search-results-mode.rb +1 -16
- data/lib/sup/modes/reply-mode.rb +18 -31
- data/lib/sup/modes/resume-mode.rb +6 -6
- data/lib/sup/modes/scroll-mode.rb +6 -5
- data/lib/sup/modes/search-results-mode.rb +6 -17
- data/lib/sup/modes/thread-index-mode.rb +70 -28
- data/lib/sup/modes/thread-view-mode.rb +65 -29
- data/lib/sup/person.rb +71 -30
- data/lib/sup/poll.rb +13 -4
- data/lib/sup/rfc2047.rb +61 -0
- data/lib/sup/sent.rb +7 -5
- data/lib/sup/source.rb +12 -9
- data/lib/sup/suicide.rb +36 -0
- data/lib/sup/tagger.rb +6 -6
- data/lib/sup/textfield.rb +76 -14
- data/lib/sup/thread.rb +97 -123
- data/lib/sup/util.rb +167 -1
- metadata +30 -5
data/bin/sup-add
CHANGED
@@ -37,6 +37,7 @@ Options are:
|
|
37
37
|
EOS
|
38
38
|
opt :archive, "Automatically archive all new messages from these sources."
|
39
39
|
opt :unusual, "Do not automatically poll these sources for new messages."
|
40
|
+
opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String
|
40
41
|
opt :force_new, "Create a new account for this source, even if one already exists."
|
41
42
|
end
|
42
43
|
|
@@ -77,36 +78,45 @@ end
|
|
77
78
|
$terminal.wrap_at = :auto
|
78
79
|
Redwood::start
|
79
80
|
index = Redwood::Index.new
|
80
|
-
index.load
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
82
|
+
index.lock_or_die
|
83
|
+
|
84
|
+
begin
|
85
|
+
index.load_sources
|
87
86
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
say ""
|
95
|
-
username, password = get_login_info uri, index.sources
|
96
|
-
Redwood::MBox::SSHLoader.new uri, username, password, nil, !$opts[:unusual], $opts[:archive]
|
97
|
-
when "imap", "imaps"
|
98
|
-
username, password = get_login_info uri, index.sources
|
99
|
-
Redwood::IMAP.new uri, username, password, nil, !$opts[:unusual], $opts[:archive]
|
100
|
-
when "maildir"
|
101
|
-
Redwood::Maildir.new uri, nil, !$opts[:unusual], $opts[:archive]
|
102
|
-
when "mbox"
|
103
|
-
Redwood::MBox::Loader.new uri, nil, !$opts[:unusual], $opts[:archive]
|
104
|
-
else
|
105
|
-
Trollop::die "Unknown source type #{parsed_uri.scheme.inspect}"
|
87
|
+
ARGV.each do |uri|
|
88
|
+
labels = $opts[:labels] ? $opts[:labels].split(/\s*,\s*/).uniq : []
|
89
|
+
|
90
|
+
if !$opts[:force_new] && index.source_for(uri)
|
91
|
+
say "Already know about #{uri}; skipping."
|
92
|
+
next
|
106
93
|
end
|
107
|
-
say "Adding #{source}..."
|
108
|
-
index.add_source source
|
109
|
-
end
|
110
94
|
|
111
|
-
|
112
|
-
|
95
|
+
parsed_uri = URI(uri)
|
96
|
+
Trollop::die "no path component to uri: #{parsed_uri}" unless parsed_uri.path
|
97
|
+
|
98
|
+
source =
|
99
|
+
case parsed_uri.scheme
|
100
|
+
when "mbox+ssh"
|
101
|
+
say "For SSH connections, if you will use public key authentication, you may leave the username and password blank."
|
102
|
+
say ""
|
103
|
+
username, password = get_login_info uri, index.sources
|
104
|
+
Redwood::MBox::SSHLoader.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, labels
|
105
|
+
when "imap", "imaps"
|
106
|
+
username, password = get_login_info uri, index.sources
|
107
|
+
Redwood::IMAP.new uri, username, password, nil, !$opts[:unusual], $opts[:archive], nil, labels
|
108
|
+
when "maildir"
|
109
|
+
Redwood::Maildir.new uri, nil, !$opts[:unusual], $opts[:archive], nil, labels
|
110
|
+
when "mbox"
|
111
|
+
Redwood::MBox::Loader.new uri, nil, !$opts[:unusual], $opts[:archive], nil, labels
|
112
|
+
else
|
113
|
+
Trollop::die "Unknown source type #{parsed_uri.scheme.inspect}"
|
114
|
+
end
|
115
|
+
say "Adding #{source}..."
|
116
|
+
index.add_source source
|
117
|
+
end
|
118
|
+
ensure
|
119
|
+
index.save
|
120
|
+
index.unlock
|
121
|
+
Redwood::finish
|
122
|
+
end
|
data/bin/sup-config
CHANGED
@@ -21,11 +21,11 @@ end #' stupid ruby-mode
|
|
21
21
|
|
22
22
|
def axe q, default=nil
|
23
23
|
ans =
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
if default && !default.empty?
|
25
|
+
ask "#{q} (enter for \"#{default}\"): "
|
26
|
+
else
|
27
|
+
ask "#{q}: "
|
28
|
+
end
|
29
29
|
ans.empty? ? default : ans
|
30
30
|
end
|
31
31
|
|
@@ -42,7 +42,7 @@ def add_source
|
|
42
42
|
|
43
43
|
say "Ok, adding a new source."
|
44
44
|
choose do |menu|
|
45
|
-
menu.prompt = "What type of mail source is it?"
|
45
|
+
menu.prompt = "What type of mail source is it? "
|
46
46
|
menu.choice("mbox file") { type = :mbox }
|
47
47
|
menu.choice("maildir directory") { type = :maildir }
|
48
48
|
menu.choice("remote mbox file (accessible via ssh)") { type = :mboxssh }
|
@@ -52,34 +52,57 @@ def add_source
|
|
52
52
|
end
|
53
53
|
|
54
54
|
while true do
|
55
|
-
say "
|
55
|
+
say "Ok, now for the details."
|
56
56
|
|
57
|
-
components =
|
57
|
+
default_labels, components =
|
58
58
|
case type
|
59
59
|
when :mbox
|
60
|
-
|
60
|
+
$last_fn ||= ENV["MAIL"]
|
61
|
+
fn = axe "What's the full path to the mbox file?", $last_fn #"srm
|
61
62
|
return if fn.nil? || fn.empty?
|
62
|
-
|
63
|
+
|
64
|
+
$last_fn = fn
|
65
|
+
[Redwood::MBox::Loader.suggest_labels_for(fn),
|
66
|
+
{ :scheme => "mbox", :path => fn }]
|
63
67
|
when :maildir
|
64
|
-
|
68
|
+
$last_fn ||= ENV["MAIL"]
|
69
|
+
fn = axe "What's the full path to the maildir directory?", $last_fn #"srm
|
65
70
|
return if fn.nil? || fn.empty?
|
66
|
-
|
71
|
+
|
72
|
+
$last_fn = fn
|
73
|
+
[Redwood::Maildir.suggest_labels_for(fn),
|
74
|
+
{ :scheme => "maildir", :path => fn }]
|
67
75
|
when :mboxssh
|
68
|
-
|
76
|
+
$last_server ||= "localhost"
|
77
|
+
srv = axe "What machine is the mbox file located on?", $last_server
|
69
78
|
return if srv.nil? || srv.empty?
|
70
79
|
$last_server = srv
|
71
|
-
|
80
|
+
|
81
|
+
fn = axe "What's the path to the mbox file?", $last_fn #" stupid ruby-mode
|
72
82
|
return if fn.nil? || fn.empty?
|
83
|
+
$last_fn = fn
|
73
84
|
fn = "/#{fn}" # lame
|
74
|
-
|
85
|
+
[Redwood::MBox::SSHLoader.suggest_labels_for(fn),
|
86
|
+
{ :scheme => "mbox+ssh", :host => srv, :path => fn }]
|
75
87
|
when :imap, :imaps
|
76
|
-
|
88
|
+
$last_server ||= "localhost"
|
89
|
+
srv = axe "What is the IMAP server (host, or host:port notation)?", $last_server
|
77
90
|
return if srv.nil? || srv.empty?
|
78
91
|
$last_server = srv
|
79
|
-
|
92
|
+
|
93
|
+
$last_folder ||= "INBOX"
|
94
|
+
fn = axe "What's the folder path?", $last_folder #"srm
|
80
95
|
return if fn.nil? || fn.empty?
|
96
|
+
$last_folder = fn
|
97
|
+
|
81
98
|
fn = "/#{fn}" # lame
|
82
|
-
|
99
|
+
if srv =~ /^(\w+):(\d+)$/
|
100
|
+
host, port = $1, $2.to_i
|
101
|
+
else
|
102
|
+
host, port = srv, nil
|
103
|
+
end
|
104
|
+
[Redwood::IMAP.suggest_labels_for(fn),
|
105
|
+
{ :scheme => type.to_s, :host => host, :port => port, :path => fn }]
|
83
106
|
end
|
84
107
|
|
85
108
|
uri =
|
@@ -90,17 +113,27 @@ def add_source
|
|
90
113
|
if axe_yes("Try again?") then next else return end
|
91
114
|
end
|
92
115
|
|
93
|
-
say "I'm going to add this source: #{uri}
|
116
|
+
say "I'm going to add this source: #{uri}"
|
94
117
|
unless axe("Does that look right?", "y") =~ /^y|yes$/i
|
95
118
|
if axe_yes("Try again?") then next else return end
|
96
119
|
end
|
97
120
|
|
98
121
|
usual = axe_yes "Does this source ever receive new messages?", "y"
|
99
|
-
archive = usual ? axe_yes("Should
|
122
|
+
archive = usual ? axe_yes("Should new messages be automatically archived? (I.e. not appear in your inbox, though still be accessible via search.)") : false
|
123
|
+
|
124
|
+
labels_str = axe("Enter any labels to be automatically added to all messages from this source, separated by spaces (or 'none')", default_labels.join(","))
|
125
|
+
|
126
|
+
labels =
|
127
|
+
if labels_str =~ /^\s*none\s*$/i
|
128
|
+
nil
|
129
|
+
else
|
130
|
+
labels_str.split(/\s+/)
|
131
|
+
end
|
100
132
|
|
101
133
|
cmd = build_cmd "sup-add"
|
102
134
|
cmd += " --unusual" unless usual
|
103
135
|
cmd += " --archive" if archive
|
136
|
+
cmd += " --labels=#{labels.join(',')}" if labels && !labels.empty?
|
104
137
|
cmd += " #{uri}"
|
105
138
|
|
106
139
|
puts "Ok, trying to run \"#{cmd}\"..."
|
@@ -128,8 +161,7 @@ program. Get ready to be the envy of everyone in your internets
|
|
128
161
|
with your amazing keyboarding skills! Jump from email to email with
|
129
162
|
nary a click of the mouse!
|
130
163
|
|
131
|
-
Just answer these simple questions and you'll be on your way
|
132
|
-
enter at any point to accept the default answer.
|
164
|
+
Just answer these simple questions and you'll be on your way.
|
133
165
|
|
134
166
|
EOS
|
135
167
|
#' stupid ruby-mode
|
@@ -137,7 +169,7 @@ EOS
|
|
137
169
|
account = $config[:accounts][:default]
|
138
170
|
|
139
171
|
name = axe "What's your name?", account[:name]
|
140
|
-
email = axe "What's your email address?", account[:email] #'srm
|
172
|
+
email = axe "What's your (primary) email address?", account[:email] #'srm
|
141
173
|
|
142
174
|
say "Ok, your header will look like this:"
|
143
175
|
say " From: #{name} <#{email}>"
|
@@ -172,7 +204,7 @@ until done
|
|
172
204
|
|
173
205
|
say "\n"
|
174
206
|
choose do |menu|
|
175
|
-
menu.prompt = "Your wish?"
|
207
|
+
menu.prompt = "Your wish? "
|
176
208
|
menu.choice("Add a new source.") { add_source }
|
177
209
|
menu.choice("Done adding sources!") { done = true }
|
178
210
|
end
|
@@ -184,12 +216,6 @@ Ok. The final step is to import all your messages into the Sup index.
|
|
184
216
|
Depending on how many messages are in the sources, this could take
|
185
217
|
quite a while.
|
186
218
|
|
187
|
-
IMPORTANT NOTE: this import will archive messages if the source is
|
188
|
-
marked archival, and won't otherwise. It will preserve read/unread
|
189
|
-
status as given by the source, and it will automatically add one label
|
190
|
-
per source. All of this behavior can be controlled on per-source
|
191
|
-
basis by running sup-sync manually.
|
192
|
-
|
193
219
|
EOS
|
194
220
|
#'
|
195
221
|
if axe_yes "Run sup-sync to import all messages now?"
|
@@ -216,7 +242,7 @@ like you're ready to jack in to cyberspace there, cowboy.
|
|
216
242
|
|
217
243
|
Just one last command:
|
218
244
|
|
219
|
-
sup
|
245
|
+
#{build_cmd "sup"}
|
220
246
|
|
221
247
|
Have fun!
|
222
248
|
EOS
|
data/bin/sup-sync
CHANGED
@@ -43,8 +43,8 @@ Usage:
|
|
43
43
|
sup-sync [options] <source>*
|
44
44
|
|
45
45
|
where <source>* is zero or more source URIs. If no sources are given,
|
46
|
-
sync from all usual sources.
|
47
|
-
|
46
|
+
sync from all usual sources. Supported source URI schemes can be seen
|
47
|
+
by running "sup-add --help".
|
48
48
|
|
49
49
|
Options controlling WHICH messages sup-sync operates on:
|
50
50
|
EOS
|
@@ -89,7 +89,6 @@ op = [:asis, :restore, :discard].find { |x| opts[x] } || :asis
|
|
89
89
|
|
90
90
|
Redwood::start
|
91
91
|
index = Redwood::Index.new
|
92
|
-
index.load
|
93
92
|
|
94
93
|
restored_state =
|
95
94
|
if opts[:restore]
|
@@ -106,24 +105,26 @@ restored_state =
|
|
106
105
|
{}
|
107
106
|
end
|
108
107
|
|
109
|
-
sources = ARGV.map do |uri|
|
110
|
-
uri = "mbox://#{uri}" unless uri =~ %r!://!
|
111
|
-
index.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
112
|
-
end
|
113
|
-
|
114
|
-
sources = index.usual_sources if sources.empty?
|
115
|
-
sources = index.sources if opts[:all_sources]
|
116
|
-
|
117
|
-
unless target == :new
|
118
|
-
if opts[:start_at]
|
119
|
-
sources.each { |s| s.seek_to! opts[:start_at] }
|
120
|
-
else
|
121
|
-
sources.each { |s| s.reset! }
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
108
|
seen = {}
|
109
|
+
index.lock_or_die
|
126
110
|
begin
|
111
|
+
index.load
|
112
|
+
|
113
|
+
sources = ARGV.map do |uri|
|
114
|
+
index.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
115
|
+
end
|
116
|
+
|
117
|
+
sources = index.usual_sources if sources.empty?
|
118
|
+
sources = index.sources if opts[:all_sources]
|
119
|
+
|
120
|
+
unless target == :new
|
121
|
+
if opts[:start_at]
|
122
|
+
sources.each { |s| s.seek_to! opts[:start_at] }
|
123
|
+
else
|
124
|
+
sources.each { |s| s.reset! }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
127
128
|
sources.each do |source|
|
128
129
|
$stderr.puts "Scanning #{source}..."
|
129
130
|
num_added = num_updated = num_scanned = num_restored = 0
|
@@ -148,7 +149,7 @@ begin
|
|
148
149
|
|
149
150
|
## skip if we're operating on restored messages, and this one
|
150
151
|
## ain't.
|
151
|
-
next if target == :restored && (!restored_state[m.id] || restored_state[m.id].sort_by { |s| s.to_s } == index_state.sort_by { |s| s.to_s })
|
152
|
+
next if target == :restored && (!restored_state[m.id] || (index_state && restored_state[m.id].sort_by { |s| s.to_s } == index_state.sort_by { |s| s.to_s }))
|
152
153
|
|
153
154
|
## m.labels is the default source labels. tweak these according
|
154
155
|
## to default source state modification flags.
|
@@ -177,7 +178,7 @@ begin
|
|
177
178
|
elapsed = last_info_time - start_time
|
178
179
|
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
|
179
180
|
remaining = (100.0 - pctdone) * (elapsed.to_f / pctdone)
|
180
|
-
$stderr.puts "## #{
|
181
|
+
$stderr.puts "## #{num_scanned} (#{pctdone}%) read; #{elapsed.to_time_s} elapsed; #{remaining.to_time_s} remaining"
|
181
182
|
end
|
182
183
|
|
183
184
|
if index_state.nil?
|
@@ -193,43 +194,48 @@ begin
|
|
193
194
|
$stderr.puts "Scanned #{num_scanned}, added #{num_added}, updated #{num_updated} messages from #{source}."
|
194
195
|
$stderr.puts "Restored state on #{num_restored} (#{100.0 * num_restored / num_scanned}%) messages." if num_restored > 0
|
195
196
|
end
|
197
|
+
|
198
|
+
## delete any messages in the index that claim they're from one of
|
199
|
+
## these sources, but that we didn't see.
|
200
|
+
##
|
201
|
+
## kinda crappy code here, because we delve directly into the Ferret
|
202
|
+
## API.
|
203
|
+
##
|
204
|
+
## TODO: move this to Index, i suppose.
|
205
|
+
|
206
|
+
|
207
|
+
if target == :all || target == :changed
|
208
|
+
$stderr.puts "Deleting missing messages from the index..."
|
209
|
+
num_del, num_scanned = 0, 0
|
210
|
+
sources.each do |source|
|
211
|
+
raise "no source id for #{source}" unless source.id
|
212
|
+
q = "+source_id:#{source.id}"
|
213
|
+
q += " +source_info: >= #{opts[:start_at]}" if opts[:start_at]
|
214
|
+
index.index.search_each(q, :limit => :all) do |docid, score|
|
215
|
+
num_scanned += 1
|
216
|
+
mid = index.index[docid][:message_id]
|
217
|
+
unless seen[mid]
|
218
|
+
puts "Deleting #{mid}" if opts[:verbose]
|
219
|
+
index.index.delete docid unless opts[:dry_run]
|
220
|
+
num_del += 1
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
$stderr.puts "Deleted #{num_del} / #{num_scanned} messages"
|
225
|
+
end
|
226
|
+
|
227
|
+
if opts[:optimize]
|
228
|
+
$stderr.puts "Optimizing index..."
|
229
|
+
optt = time { index.index.optimize unless opts[:dry_run] }
|
230
|
+
$stderr.puts "Optimized index of size #{index.size} in #{optt}s."
|
231
|
+
end
|
232
|
+
rescue Redwood::FatalSourceError => e
|
233
|
+
$stderr.puts "Sorry, I couldn't communicate with a source: #{e.message}"
|
196
234
|
rescue Exception => e
|
197
235
|
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
198
236
|
raise
|
199
237
|
ensure
|
200
238
|
index.save
|
201
239
|
Redwood::finish
|
202
|
-
|
203
|
-
|
204
|
-
## delete any messages in the index that claim they're from one of
|
205
|
-
## these sources, but that we didn't see.
|
206
|
-
##
|
207
|
-
## kinda crappy code here, because we delve directly into the Ferret
|
208
|
-
## API.
|
209
|
-
##
|
210
|
-
## TODO: move this to Index, i suppose.
|
211
|
-
if target == :all || target == :changed
|
212
|
-
$stderr.puts "Deleting missing messages from the index..."
|
213
|
-
num_del, num_scanned = 0, 0
|
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: >= #{opts[:start_at]}" if opts[:start_at]
|
218
|
-
index.index.search_each(q, :limit => :all) do |docid, score|
|
219
|
-
num_scanned += 1
|
220
|
-
mid = index.index[docid][:message_id]
|
221
|
-
unless seen[mid]
|
222
|
-
puts "Deleting #{mid}" if opts[:verbose]
|
223
|
-
index.index.delete docid unless opts[:dry_run]
|
224
|
-
num_del += 1
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
$stderr.puts "Deleted #{num_del} / #{num_scanned} messages"
|
229
|
-
end
|
230
|
-
|
231
|
-
if opts[:optimize]
|
232
|
-
$stderr.puts "Optimizing index..."
|
233
|
-
optt = time { index.index.optimize unless opts[:dry_run] }
|
234
|
-
$stderr.puts "Optimized index of size #{index.size} in #{optt}s."
|
240
|
+
index.unlock
|
235
241
|
end
|
data/bin/sup-sync-back
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'uri'
|
5
|
+
require 'tempfile'
|
6
|
+
require 'trollop'
|
7
|
+
require "sup"
|
8
|
+
|
9
|
+
## save a message 'm' to an open file pointer 'fp'
|
10
|
+
def save m, fp
|
11
|
+
m.source.each_raw_full_message_line(m.source_info) { |l| fp.print l }
|
12
|
+
end
|
13
|
+
|
14
|
+
opts = Trollop::options do
|
15
|
+
version "sup-sync-back (sup #{Redwood::VERSION})"
|
16
|
+
banner <<EOS
|
17
|
+
Partially synchronizes a message source with the Sup index by deleting
|
18
|
+
or moving any messages from the source that are marked as deleted or
|
19
|
+
spam in the Sup index.
|
20
|
+
|
21
|
+
Currently only works with mbox sources.
|
22
|
+
|
23
|
+
Usage:
|
24
|
+
sup-sync-back [options] <source>*
|
25
|
+
|
26
|
+
where <source>* is zero or more source URIs. If no sources are given,
|
27
|
+
sync back all usual sources.
|
28
|
+
|
29
|
+
You probably want to run sup-sync --changed after this command.
|
30
|
+
|
31
|
+
Options include:
|
32
|
+
EOS
|
33
|
+
opt :delete_deleted, "Delete deleted messages.", :default => false, :short => "d"
|
34
|
+
opt :move_deleted, "Move deleted messages to a local mbox file.", :type => String, :short => :none
|
35
|
+
opt :delete_spam, "Delete spam messages.", :default => false, :short => "s"
|
36
|
+
opt :move_spam, "Move spam messages to a local mbox file.", :type => String, :short => :none
|
37
|
+
opt :verbose, "Print message ids as they're processed."
|
38
|
+
opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
|
39
|
+
opt :version, "Show version information", :short => :none
|
40
|
+
|
41
|
+
conflicts :delete_deleted, :move_deleted
|
42
|
+
conflicts :delete_spam, :move_spam
|
43
|
+
end
|
44
|
+
|
45
|
+
Redwood::start
|
46
|
+
index = Redwood::Index.new
|
47
|
+
index.lock_or_die
|
48
|
+
|
49
|
+
deleted_fp, spam_fp = nil
|
50
|
+
unless opts[:dry_run]
|
51
|
+
deleted_fp = File.open(opts[:move_deleted], "a") if opts[:move_deleted]
|
52
|
+
spam_fp = File.open(opts[:move_spam], "a") if opts[:move_spam]
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
index.load
|
57
|
+
|
58
|
+
sources = ARGV.map do |uri|
|
59
|
+
s = index.source_for(uri) or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
|
60
|
+
s.is_a?(Redwood::MBox::Loader) or Trollop::die "#{uri} is not an mbox source."
|
61
|
+
s
|
62
|
+
end
|
63
|
+
|
64
|
+
if sources.empty?
|
65
|
+
sources = index.usual_sources.select { |s| s.is_a? Redwood::MBox::Loader }
|
66
|
+
end
|
67
|
+
|
68
|
+
modified_sources = []
|
69
|
+
sources.each do |source|
|
70
|
+
$stderr.puts "Scanning #{source}..."
|
71
|
+
|
72
|
+
unless ((opts[:delete_deleted] || opts[:move_deleted]) && index.has_any_from_source_with_label?(source, :deleted)) || ((opts[:delete_spam] || opts[:move_spam]) && index.has_any_from_source_with_label?(source, :spam))
|
73
|
+
$stderr.puts "Nothing to do from this source; skipping"
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
source.reset!
|
78
|
+
num_deleted = num_moved = num_scanned = 0
|
79
|
+
|
80
|
+
out_fp = Tempfile.new "sup-sync-back-#{source.id}"
|
81
|
+
Redwood::PollManager.add_messages_from source do |m, offset, entry|
|
82
|
+
num_scanned += 1
|
83
|
+
|
84
|
+
if entry
|
85
|
+
labels = entry[:label].split.map { |x| x.intern }.to_boolean_h
|
86
|
+
|
87
|
+
if labels.member? :deleted
|
88
|
+
if opts[:delete_deleted]
|
89
|
+
puts "Dropping deleted message #{source}##{offset}" if opts[:verbose]
|
90
|
+
num_deleted += 1
|
91
|
+
elsif opts[:move_deleted] && labels.member?(:deleted)
|
92
|
+
puts "Moving deleted message #{source}##{offset}" if opts[:verbose]
|
93
|
+
save m, deleted_fp unless opts[:dry_run]
|
94
|
+
num_moved += 1
|
95
|
+
end
|
96
|
+
|
97
|
+
elsif labels.member? :spam
|
98
|
+
if opts[:delete_spam]
|
99
|
+
puts "Deleting spam message #{source}##{offset}" if opts[:verbose]
|
100
|
+
num_deleted += 1
|
101
|
+
elsif opts[:move_spam] && labels.member?(:spam)
|
102
|
+
puts "Moving spam message #{source}##{offset}" if opts[:verbose]
|
103
|
+
save m, spam_fp unless opts[:dry_run]
|
104
|
+
num_moved += 1
|
105
|
+
end
|
106
|
+
else
|
107
|
+
save m, out_fp unless opts[:dry_run]
|
108
|
+
end
|
109
|
+
else
|
110
|
+
save m, out_fp unless opts[:dry_run]
|
111
|
+
end
|
112
|
+
|
113
|
+
nil # don't actually add anything!
|
114
|
+
end
|
115
|
+
$stderr.puts "Scanned #{num_scanned}, deleted #{num_deleted}, moved #{num_moved} messages from #{source}."
|
116
|
+
modified_sources << source if num_deleted > 0 || num_moved > 0
|
117
|
+
out_fp.close unless opts[:dry_run]
|
118
|
+
|
119
|
+
unless opts[:dry_run] || (num_deleted == 0 && num_moved == 0)
|
120
|
+
deleted_fp.flush if deleted_fp
|
121
|
+
spam_fp.flush if spam_fp
|
122
|
+
$stderr.puts "Moving #{out_fp.path} to #{source.file_path}"
|
123
|
+
FileUtils.mv out_fp.path, source.file_path
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
unless opts[:dry_run]
|
128
|
+
deleted_fp.close if deleted_fp
|
129
|
+
spam_fp.close if spam_fp
|
130
|
+
end
|
131
|
+
|
132
|
+
$stderr.puts "Done."
|
133
|
+
unless modified_sources.empty?
|
134
|
+
$stderr.puts "You should now run: sup-sync --changed #{modified_sources.join(' ')}"
|
135
|
+
end
|
136
|
+
rescue Exception => e
|
137
|
+
File.open("sup-exception-log.txt", "w") { |f| f.puts e.backtrace }
|
138
|
+
raise
|
139
|
+
ensure
|
140
|
+
index.save
|
141
|
+
Redwood::finish
|
142
|
+
index.unlock
|
143
|
+
end
|