sup 0.19.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.gitmodules +3 -0
- data/.travis.yml +3 -2
- data/CONTRIBUTORS +19 -13
- data/Gemfile +4 -0
- data/History.txt +41 -0
- data/Rakefile +41 -1
- data/ReleaseNotes +17 -0
- data/bin/sup +5 -18
- data/bin/sup-add +1 -2
- data/bin/sup-config +0 -1
- data/bin/sup-dump +0 -1
- data/bin/sup-import-dump +1 -2
- data/bin/sup-sync +0 -1
- data/bin/sup-sync-back-maildir +1 -2
- data/bin/sup-tweak-labels +1 -2
- data/contrib/colorpicker.rb +0 -2
- data/contrib/completion/_sup.bash +102 -0
- data/devel/profile.rb +0 -1
- data/ext/mkrf_conf_xapian.rb +47 -0
- data/lib/sup.rb +9 -8
- data/lib/sup/buffer.rb +12 -0
- data/lib/sup/colormap.rb +5 -2
- data/lib/sup/contact.rb +4 -2
- data/lib/sup/crypto.rb +41 -8
- data/lib/sup/draft.rb +8 -8
- data/lib/sup/hook.rb +1 -1
- data/lib/sup/index.rb +2 -2
- data/lib/sup/label.rb +1 -1
- data/lib/sup/maildir.rb +16 -5
- data/lib/sup/mbox.rb +13 -5
- data/lib/sup/message.rb +17 -3
- data/lib/sup/message_chunks.rb +10 -2
- data/lib/sup/mode.rb +33 -28
- data/lib/sup/modes/edit_message_mode.rb +3 -2
- data/lib/sup/modes/forward_mode.rb +22 -3
- data/lib/sup/modes/line_cursor_mode.rb +1 -1
- data/lib/sup/modes/text_mode.rb +6 -1
- data/lib/sup/modes/thread_index_mode.rb +11 -1
- data/lib/sup/modes/thread_view_mode.rb +103 -9
- data/lib/sup/person.rb +68 -61
- data/lib/sup/search.rb +1 -1
- data/lib/sup/sent.rb +1 -1
- data/lib/sup/util.rb +1 -75
- data/lib/sup/util/locale_fiddler.rb +24 -0
- data/lib/sup/version.rb +1 -1
- data/sup.gemspec +22 -5
- data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
- data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
- data/test/fixtures/blank-header-fields.eml +71 -0
- data/test/fixtures/contacts.txt +1 -0
- data/test/fixtures/malicious-attachment-names.eml +55 -0
- data/test/fixtures/missing-from-to.eml +18 -0
- data/test/{messages → fixtures}/missing-line.eml +0 -0
- data/test/fixtures/multi-part-2.eml +72 -0
- data/test/fixtures/multi-part.eml +61 -0
- data/test/fixtures/no-body.eml +18 -0
- data/test/fixtures/simple-message.eml +29 -0
- data/test/gnupg_test_home/gpg.conf +2 -1
- data/test/gnupg_test_home/key1.gen +15 -0
- data/test/gnupg_test_home/key2.gen +15 -0
- data/test/gnupg_test_home/key_ecc.gen +13 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/regen_keys.sh +38 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +22 -17
- data/test/integration/test_maildir.rb +75 -0
- data/test/integration/test_mbox.rb +69 -0
- data/test/test_crypto.rb +12 -2
- data/test/test_header_parsing.rb +1 -1
- data/test/test_helper.rb +6 -3
- data/test/test_message.rb +42 -342
- data/test/test_messages_dir.rb +4 -28
- data/test/test_yaml_regressions.rb +1 -1
- data/test/unit/test_contact.rb +33 -0
- data/test/unit/test_locale_fiddler.rb +15 -0
- data/test/unit/test_person.rb +37 -0
- metadata +108 -38
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
data/devel/profile.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/command.rb'
|
3
|
+
require 'rubygems/dependency_installer.rb'
|
4
|
+
require 'rbconfig'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Gem::Command.build_args = ARGV
|
8
|
+
rescue NoMethodError
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "xapian: platform specific dependencies.."
|
12
|
+
|
13
|
+
inst = Gem::DependencyInstaller.new
|
14
|
+
begin
|
15
|
+
|
16
|
+
if !RbConfig::CONFIG['arch'].include?('openbsd')
|
17
|
+
# update version in Gemfile as well
|
18
|
+
name = "xapian-ruby"
|
19
|
+
version = "~> 1.2.15"
|
20
|
+
|
21
|
+
begin
|
22
|
+
# try to load gem
|
23
|
+
|
24
|
+
gem name, version
|
25
|
+
STDERR.puts "xapian: already installed."
|
26
|
+
|
27
|
+
rescue Gem::LoadError
|
28
|
+
|
29
|
+
STDERR.puts "xapian: installing xapian-ruby.."
|
30
|
+
inst.install name, version
|
31
|
+
|
32
|
+
end
|
33
|
+
else
|
34
|
+
STDERR.puts "xapian: openbsd: you have to install xapian-core and xapian-bindings manually, have a look at: https://github.com/sup-heliotrope/sup/wiki/Installation%3A-OpenBSD"
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue
|
38
|
+
|
39
|
+
exit(1)
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# create dummy rakefile to indicate success
|
44
|
+
f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w")
|
45
|
+
f.write("task :default\n")
|
46
|
+
f.close
|
47
|
+
|
data/lib/sup.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
3
|
require 'yaml'
|
5
4
|
require 'zlib'
|
6
5
|
require 'thread'
|
@@ -8,6 +7,7 @@ require 'fileutils'
|
|
8
7
|
require 'locale'
|
9
8
|
require 'ncursesw'
|
10
9
|
require 'rmail'
|
10
|
+
require 'uri'
|
11
11
|
begin
|
12
12
|
require 'fastthread'
|
13
13
|
rescue LoadError
|
@@ -64,6 +64,7 @@ module Redwood
|
|
64
64
|
LEGACY_YAML_DOMAIN = "masanjin.net"
|
65
65
|
YAML_DATE = "2006-10-01"
|
66
66
|
MAILDIR_SYNC_CHECK_SKIPPED = 'SKIPPED'
|
67
|
+
URI_ENCODE_CHARS = "!*'();:@&=+$,?#[] " # see https://en.wikipedia.org/wiki/Percent-encoding
|
67
68
|
|
68
69
|
## record exceptions thrown in threads nicely
|
69
70
|
@exceptions = []
|
@@ -103,7 +104,7 @@ module Redwood
|
|
103
104
|
o
|
104
105
|
end
|
105
106
|
|
106
|
-
mode = if File.
|
107
|
+
mode = if File.exist? fn
|
107
108
|
File.stat(fn).mode
|
108
109
|
else
|
109
110
|
0600
|
@@ -111,7 +112,7 @@ module Redwood
|
|
111
112
|
|
112
113
|
if backup
|
113
114
|
backup_fn = fn + '.bak'
|
114
|
-
if File.
|
115
|
+
if File.exist?(fn) && File.size(fn) > 0
|
115
116
|
File.open(backup_fn, "w", mode) do |f|
|
116
117
|
File.open(fn, "r") { |old_f| FileUtils.copy_stream old_f, f }
|
117
118
|
f.fsync
|
@@ -137,7 +138,7 @@ module Redwood
|
|
137
138
|
end
|
138
139
|
|
139
140
|
def load_yaml_obj fn, compress=false
|
140
|
-
o = if File.
|
141
|
+
o = if File.exist? fn
|
141
142
|
if compress
|
142
143
|
Zlib::GzipReader.open(fn) { |f| YAML::load f }
|
143
144
|
else
|
@@ -178,7 +179,7 @@ module Redwood
|
|
178
179
|
return if bypass_sync_check
|
179
180
|
|
180
181
|
if $config[:sync_back_to_maildir]
|
181
|
-
if not File.
|
182
|
+
if not File.exist? Redwood::SYNC_OK_FN
|
182
183
|
Redwood.warn_syncback <<EOS
|
183
184
|
It appears that the "sync_back_to_maildir" option has been changed
|
184
185
|
from false to true since the last execution of sup.
|
@@ -189,14 +190,14 @@ Should I complain about this again? (Y/n)
|
|
189
190
|
EOS
|
190
191
|
File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(Redwood::MAILDIR_SYNC_CHECK_SKIPPED) } if STDIN.gets.chomp.downcase == 'n'
|
191
192
|
end
|
192
|
-
elsif not $config[:sync_back_to_maildir] and File.
|
193
|
+
elsif not $config[:sync_back_to_maildir] and File.exist? Redwood::SYNC_OK_FN
|
193
194
|
File.delete(Redwood::SYNC_OK_FN)
|
194
195
|
end
|
195
196
|
end
|
196
197
|
|
197
198
|
def check_syncback_settings
|
198
199
|
# don't check if syncback was never performed
|
199
|
-
return unless File.
|
200
|
+
return unless File.exist? Redwood::SYNC_OK_FN
|
200
201
|
active_sync_sources = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? }
|
201
202
|
return if active_sync_sources.length == 1 and active_sync_sources[0] == Redwood::MAILDIR_SYNC_CHECK_SKIPPED
|
202
203
|
sources = SourceManager.sources
|
@@ -336,7 +337,7 @@ EOM
|
|
336
337
|
:continuous_scroll => false,
|
337
338
|
:always_edit_async => false,
|
338
339
|
}
|
339
|
-
if File.
|
340
|
+
if File.exist? filename
|
340
341
|
config = Redwood::load_yaml_obj filename
|
341
342
|
abort "#{filename} is not a valid configuration file (it's a #{config.class}, not a hash)" unless config.is_a?(Hash)
|
342
343
|
default_config.merge config
|
data/lib/sup/buffer.rb
CHANGED
@@ -396,6 +396,18 @@ EOS
|
|
396
396
|
end
|
397
397
|
end
|
398
398
|
|
399
|
+
## ask* functions. these functions display a one-line text field with
|
400
|
+
## a prompt at the bottom of the screen. answers typed or choosen by
|
401
|
+
## tab-completion
|
402
|
+
##
|
403
|
+
## common arguments are:
|
404
|
+
##
|
405
|
+
## domain: token used as key for @textfields, which seems to be a
|
406
|
+
## dictionary of input field objects
|
407
|
+
## question: string used as prompt
|
408
|
+
## completions: array of possible answers, that can be completed by using
|
409
|
+
## the tab key
|
410
|
+
## default: default value to return
|
399
411
|
def ask_with_completions domain, question, completions, default=nil
|
400
412
|
ask domain, question, default do |s|
|
401
413
|
s.fix_encoding!
|
data/lib/sup/colormap.rb
CHANGED
@@ -17,6 +17,9 @@ module Ncurses
|
|
17
17
|
|
18
18
|
## xterm 24-shade grayscale
|
19
19
|
24.times { |x| color! "g#{x}", (16+6*6*6) + x }
|
20
|
+
elsif Ncurses::NUM_COLORS == -1
|
21
|
+
## Terminal emulator doesn't appear to support colors
|
22
|
+
fail "sup must be run in a terminal with color support, please check your TERM variable."
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
@@ -186,13 +189,13 @@ class Colormap
|
|
186
189
|
## Try to use the user defined colors, in case of an error fall back
|
187
190
|
## to the default ones.
|
188
191
|
def populate_colormap
|
189
|
-
user_colors = if File.
|
192
|
+
user_colors = if File.exist? Redwood::COLOR_FN
|
190
193
|
debug "loading user colors from #{Redwood::COLOR_FN}"
|
191
194
|
Redwood::load_yaml_obj Redwood::COLOR_FN
|
192
195
|
end
|
193
196
|
|
194
197
|
## Set attachment sybmol to sane default for existing colorschemes
|
195
|
-
if user_colors and user_colors.has_key? :to_me
|
198
|
+
if user_colors and user_colors.has_key? :to_me
|
196
199
|
user_colors[:with_attachment] = user_colors[:to_me] unless user_colors.has_key? :with_attachment
|
197
200
|
end
|
198
201
|
|
data/lib/sup/contact.rb
CHANGED
@@ -16,7 +16,7 @@ class ContactManager
|
|
16
16
|
@a2p = {} # alias to person
|
17
17
|
@e2p = {} # email to person
|
18
18
|
|
19
|
-
if File.
|
19
|
+
if File.exist? fn
|
20
20
|
IO.foreach(fn) do |l|
|
21
21
|
l =~ /^([^:]*): (.*)$/ or raise "can't parse #{fn} line #{l.inspect}"
|
22
22
|
aalias, addr = $1, $2
|
@@ -29,11 +29,13 @@ class ContactManager
|
|
29
29
|
def contacts_with_aliases; @a2p.values.uniq end
|
30
30
|
|
31
31
|
def update_alias person, aalias=nil
|
32
|
+
## Deleting old data if it exists
|
32
33
|
old_aalias = @p2a[person]
|
33
|
-
if
|
34
|
+
if old_aalias
|
34
35
|
@a2p.delete old_aalias
|
35
36
|
@e2p.delete person.email
|
36
37
|
end
|
38
|
+
## Update with new data
|
37
39
|
@p2a[person] = aalias
|
38
40
|
unless aalias.nil? || aalias.empty?
|
39
41
|
@a2p[aalias] = person
|
data/lib/sup/crypto.rb
CHANGED
@@ -10,11 +10,14 @@ class CryptoManager
|
|
10
10
|
|
11
11
|
class Error < StandardError; end
|
12
12
|
|
13
|
-
OUTGOING_MESSAGE_OPERATIONS =
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
OUTGOING_MESSAGE_OPERATIONS = {
|
14
|
+
sign: "Sign",
|
15
|
+
sign_and_encrypt: "Sign and encrypt",
|
16
|
+
encrypt: "Encrypt only"
|
17
|
+
}
|
18
|
+
|
19
|
+
KEY_PATTERN = /(-----BEGIN PGP PUBLIC KEY BLOCK.*-----END PGP PUBLIC KEY BLOCK)/m
|
20
|
+
KEYSERVER_URL = "http://pool.sks-keyservers.net:11371/pks/lookup"
|
18
21
|
|
19
22
|
HookManager.register "gpg-options", <<EOS
|
20
23
|
Runs before gpg is called, allowing you to modify the options (most
|
@@ -212,9 +215,10 @@ EOS
|
|
212
215
|
unknown = false
|
213
216
|
all_output_lines = []
|
214
217
|
all_trusted = true
|
218
|
+
unknown_fingerprint = nil
|
215
219
|
|
216
220
|
verify_result.signatures.each do |signature|
|
217
|
-
output_lines, trusted = sig_output_lines signature
|
221
|
+
output_lines, trusted, unknown_fingerprint = sig_output_lines signature
|
218
222
|
all_output_lines << output_lines
|
219
223
|
all_output_lines.flatten!
|
220
224
|
all_trusted &&= trusted
|
@@ -242,6 +246,8 @@ EOS
|
|
242
246
|
end
|
243
247
|
elsif !unknown
|
244
248
|
Chunk::CryptoNotice.new(:invalid, summary_line, all_output_lines)
|
249
|
+
elsif unknown_fingerprint
|
250
|
+
Chunk::CryptoNotice.new(:unknown_key, "Unable to determine validity of cryptographic signature", all_output_lines, unknown_fingerprint)
|
245
251
|
else
|
246
252
|
unknown_status all_output_lines
|
247
253
|
end
|
@@ -351,6 +357,31 @@ EOS
|
|
351
357
|
[notice, sig, msg]
|
352
358
|
end
|
353
359
|
|
360
|
+
def retrieve fingerprint
|
361
|
+
require 'net/http'
|
362
|
+
uri = URI($config[:keyserver_url] || KEYSERVER_URL)
|
363
|
+
unless uri.scheme == "http" and not uri.host.nil?
|
364
|
+
return "Invalid url: #{uri}"
|
365
|
+
end
|
366
|
+
|
367
|
+
fingerprint = "0x" + fingerprint unless fingerprint[0..1] == "0x"
|
368
|
+
params = {op: "get", search: fingerprint}
|
369
|
+
uri.query = URI.encode_www_form(params)
|
370
|
+
|
371
|
+
begin
|
372
|
+
res = Net::HTTP.get_response(uri)
|
373
|
+
rescue SocketError # Host doesn't exist or we couldn't connect
|
374
|
+
end
|
375
|
+
return "Couldn't get key from keyserver at this address: #{uri}" unless res.is_a?(Net::HTTPSuccess)
|
376
|
+
|
377
|
+
match = KEY_PATTERN.match(res.body)
|
378
|
+
return "No key found" unless match && match.length > 0
|
379
|
+
|
380
|
+
GPGME::Key.import(match[0])
|
381
|
+
|
382
|
+
return nil
|
383
|
+
end
|
384
|
+
|
354
385
|
private
|
355
386
|
|
356
387
|
def unknown_status lines=[]
|
@@ -394,6 +425,7 @@ private
|
|
394
425
|
rescue EOFError
|
395
426
|
from_key = nil
|
396
427
|
first_sig = "No public key available for #{signature.fingerprint}"
|
428
|
+
unknown_fpr = signature.fingerprint
|
397
429
|
end
|
398
430
|
|
399
431
|
time_line = "Signature made " + signature.timestamp.strftime("%a %d %b %Y %H:%M:%S %Z") +
|
@@ -422,7 +454,7 @@ private
|
|
422
454
|
output_lines << HookManager.run("sig-output",
|
423
455
|
{:signature => signature, :from_key => from_key})
|
424
456
|
end
|
425
|
-
return output_lines, trusted
|
457
|
+
return output_lines, trusted, unknown_fpr
|
426
458
|
end
|
427
459
|
|
428
460
|
def key_type key, fpr
|
@@ -435,6 +467,7 @@ private
|
|
435
467
|
when GPGME::PK_DSA then "DSA "
|
436
468
|
when GPGME::PK_ELG then "ElGamel "
|
437
469
|
when GPGME::PK_ELG_E then "ElGamel "
|
470
|
+
else "unknown key type (#{subkey.pubkey_algo}) "
|
438
471
|
end
|
439
472
|
end
|
440
473
|
|
@@ -443,7 +476,7 @@ private
|
|
443
476
|
# elsif only one account, then leave blank so gpg default will be user
|
444
477
|
# else set --local-user from_email_address
|
445
478
|
# NOTE: multiple signers doesn't seem to work with gpgme (2.0.2, 1.0.8)
|
446
|
-
#
|
479
|
+
#
|
447
480
|
def gen_sign_user_opts from
|
448
481
|
account = AccountManager.account_for from
|
449
482
|
account ||= AccountManager.default_account
|
data/lib/sup/draft.rb
CHANGED
@@ -16,7 +16,7 @@ class DraftManager
|
|
16
16
|
def write_draft
|
17
17
|
offset = @source.gen_offset
|
18
18
|
fn = @source.fn_for_offset offset
|
19
|
-
File.open(fn, "w") { |f| yield f }
|
19
|
+
File.open(fn, "w:UTF-8") { |f| yield f }
|
20
20
|
PollManager.poll_from @source
|
21
21
|
end
|
22
22
|
|
@@ -33,7 +33,7 @@ class DraftLoader < Source
|
|
33
33
|
yaml_properties
|
34
34
|
|
35
35
|
def initialize dir=Redwood::DRAFT_DIR
|
36
|
-
Dir.mkdir dir unless File.
|
36
|
+
Dir.mkdir dir unless File.exist? dir
|
37
37
|
super DraftManager.source_name, true, false
|
38
38
|
@dir = dir
|
39
39
|
@cur_offset = 0
|
@@ -61,8 +61,8 @@ class DraftLoader < Source
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def gen_offset
|
64
|
-
i =
|
65
|
-
while File.
|
64
|
+
i = @cur_offset
|
65
|
+
while File.exist? fn_for_offset(i)
|
66
66
|
i += 1
|
67
67
|
end
|
68
68
|
i
|
@@ -75,7 +75,7 @@ class DraftLoader < Source
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def load_message offset
|
78
|
-
raise SourceError, "Draft not found" unless File.
|
78
|
+
raise SourceError, "Draft not found" unless File.exist? fn_for_offset(offset)
|
79
79
|
File.open fn_for_offset(offset) do |f|
|
80
80
|
RMail::Mailbox::MBoxReader.new(f).each_message do |input|
|
81
81
|
return RMail::Parser.read(input)
|
@@ -85,7 +85,7 @@ class DraftLoader < Source
|
|
85
85
|
|
86
86
|
def raw_header offset
|
87
87
|
ret = ""
|
88
|
-
File.open
|
88
|
+
File.open(fn_for_offset(offset), "r:UTF-8") do |f|
|
89
89
|
until f.eof? || (l = f.gets) =~ /^$/
|
90
90
|
ret += l
|
91
91
|
end
|
@@ -94,13 +94,13 @@ class DraftLoader < Source
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def each_raw_message_line offset
|
97
|
-
File.open(fn_for_offset(offset)) do |f|
|
97
|
+
File.open(fn_for_offset(offset), "r:UTF-8") do |f|
|
98
98
|
yield f.gets until f.eof?
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
102
|
def raw_message offset
|
103
|
-
IO.read(fn_for_offset(offset))
|
103
|
+
IO.read(fn_for_offset(offset), :encoding => "UTF-8")
|
104
104
|
end
|
105
105
|
|
106
106
|
def start_offset; 0; end
|
data/lib/sup/hook.rb
CHANGED
data/lib/sup/index.rb
CHANGED
@@ -105,7 +105,7 @@ EOS
|
|
105
105
|
|
106
106
|
def save
|
107
107
|
debug "saving index and sources..."
|
108
|
-
FileUtils.mkdir_p @dir unless File.
|
108
|
+
FileUtils.mkdir_p @dir unless File.exist? @dir
|
109
109
|
SourceManager.save_sources
|
110
110
|
save_index
|
111
111
|
end
|
@@ -116,7 +116,7 @@ EOS
|
|
116
116
|
|
117
117
|
def load_index failsafe=false
|
118
118
|
path = File.join(@dir, 'xapian')
|
119
|
-
if File.
|
119
|
+
if File.exist? path
|
120
120
|
@xapian = Xapian::WritableDatabase.new(path, Xapian::DB_OPEN)
|
121
121
|
db_version = @xapian.get_metadata 'version'
|
122
122
|
db_version = '0' if db_version.empty?
|
data/lib/sup/label.rb
CHANGED
data/lib/sup/maildir.rb
CHANGED
@@ -12,7 +12,15 @@ class Maildir < Source
|
|
12
12
|
def initialize uri, usual=true, archived=false, sync_back=true, id=nil, labels=[]
|
13
13
|
super uri, usual, archived, id
|
14
14
|
@expanded_uri = Source.expand_filesystem_uri(uri)
|
15
|
-
|
15
|
+
parts = @expanded_uri.match /^([a-zA-Z0-9]*:(\/\/)?)(.*)/
|
16
|
+
if parts
|
17
|
+
prefix = parts[1]
|
18
|
+
@path = parts[3]
|
19
|
+
uri = URI(prefix + URI.encode(@path, URI_ENCODE_CHARS))
|
20
|
+
else
|
21
|
+
uri = URI(URI.encode @expanded_uri, URI_ENCODE_CHARS)
|
22
|
+
@path = uri.path
|
23
|
+
end
|
16
24
|
|
17
25
|
raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
|
18
26
|
raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
|
@@ -22,7 +30,7 @@ class Maildir < Source
|
|
22
30
|
# sync by default if not specified
|
23
31
|
@sync_back = true if @sync_back.nil?
|
24
32
|
|
25
|
-
@dir = uri.path
|
33
|
+
@dir = URI.decode uri.path
|
26
34
|
@labels = Set.new(labels || [])
|
27
35
|
@mutex = Mutex.new
|
28
36
|
@ctimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }
|
@@ -60,7 +68,7 @@ class Maildir < Source
|
|
60
68
|
File.safe_link tmp_path, new_path
|
61
69
|
stored = true
|
62
70
|
ensure
|
63
|
-
File.unlink tmp_path if File.
|
71
|
+
File.unlink tmp_path if File.exist? tmp_path
|
64
72
|
end
|
65
73
|
end #rescue Errno...
|
66
74
|
end #Dir.chdir
|
@@ -120,7 +128,10 @@ class Maildir < Source
|
|
120
128
|
@ctimes[d] = ctime
|
121
129
|
|
122
130
|
old_ids = benchmark(:maildir_read_index) { Index.instance.enum_for(:each_source_info, self.id, "#{d}/").to_a }
|
123
|
-
new_ids = benchmark(:maildir_read_dir) {
|
131
|
+
new_ids = benchmark(:maildir_read_dir) {
|
132
|
+
Dir.open(subdir).select {
|
133
|
+
|f| !File.directory? f}.map {
|
134
|
+
|x| File.join(d,File.basename(x)) }.sort }
|
124
135
|
added += new_ids - old_ids
|
125
136
|
deleted += old_ids - new_ids
|
126
137
|
debug "#{old_ids.size} in index, #{new_ids.size} in filesystem"
|
@@ -190,7 +201,7 @@ class Maildir < Source
|
|
190
201
|
def trashed? id; maildir_data(id)[2].include? "T"; end
|
191
202
|
|
192
203
|
def valid? id
|
193
|
-
File.
|
204
|
+
File.exist? File.join(@dir, id)
|
194
205
|
end
|
195
206
|
|
196
207
|
private
|