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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +3 -2
  5. data/CONTRIBUTORS +19 -13
  6. data/Gemfile +4 -0
  7. data/History.txt +41 -0
  8. data/Rakefile +41 -1
  9. data/ReleaseNotes +17 -0
  10. data/bin/sup +5 -18
  11. data/bin/sup-add +1 -2
  12. data/bin/sup-config +0 -1
  13. data/bin/sup-dump +0 -1
  14. data/bin/sup-import-dump +1 -2
  15. data/bin/sup-sync +0 -1
  16. data/bin/sup-sync-back-maildir +1 -2
  17. data/bin/sup-tweak-labels +1 -2
  18. data/contrib/colorpicker.rb +0 -2
  19. data/contrib/completion/_sup.bash +102 -0
  20. data/devel/profile.rb +0 -1
  21. data/ext/mkrf_conf_xapian.rb +47 -0
  22. data/lib/sup.rb +9 -8
  23. data/lib/sup/buffer.rb +12 -0
  24. data/lib/sup/colormap.rb +5 -2
  25. data/lib/sup/contact.rb +4 -2
  26. data/lib/sup/crypto.rb +41 -8
  27. data/lib/sup/draft.rb +8 -8
  28. data/lib/sup/hook.rb +1 -1
  29. data/lib/sup/index.rb +2 -2
  30. data/lib/sup/label.rb +1 -1
  31. data/lib/sup/maildir.rb +16 -5
  32. data/lib/sup/mbox.rb +13 -5
  33. data/lib/sup/message.rb +17 -3
  34. data/lib/sup/message_chunks.rb +10 -2
  35. data/lib/sup/mode.rb +33 -28
  36. data/lib/sup/modes/edit_message_mode.rb +3 -2
  37. data/lib/sup/modes/forward_mode.rb +22 -3
  38. data/lib/sup/modes/line_cursor_mode.rb +1 -1
  39. data/lib/sup/modes/text_mode.rb +6 -1
  40. data/lib/sup/modes/thread_index_mode.rb +11 -1
  41. data/lib/sup/modes/thread_view_mode.rb +103 -9
  42. data/lib/sup/person.rb +68 -61
  43. data/lib/sup/search.rb +1 -1
  44. data/lib/sup/sent.rb +1 -1
  45. data/lib/sup/util.rb +1 -75
  46. data/lib/sup/util/locale_fiddler.rb +24 -0
  47. data/lib/sup/version.rb +1 -1
  48. data/sup.gemspec +22 -5
  49. data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
  50. data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
  51. data/test/fixtures/blank-header-fields.eml +71 -0
  52. data/test/fixtures/contacts.txt +1 -0
  53. data/test/fixtures/malicious-attachment-names.eml +55 -0
  54. data/test/fixtures/missing-from-to.eml +18 -0
  55. data/test/{messages → fixtures}/missing-line.eml +0 -0
  56. data/test/fixtures/multi-part-2.eml +72 -0
  57. data/test/fixtures/multi-part.eml +61 -0
  58. data/test/fixtures/no-body.eml +18 -0
  59. data/test/fixtures/simple-message.eml +29 -0
  60. data/test/gnupg_test_home/gpg.conf +2 -1
  61. data/test/gnupg_test_home/key1.gen +15 -0
  62. data/test/gnupg_test_home/key2.gen +15 -0
  63. data/test/gnupg_test_home/key_ecc.gen +13 -0
  64. data/test/gnupg_test_home/pubring.gpg +0 -0
  65. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  66. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  67. data/test/gnupg_test_home/regen_keys.sh +38 -0
  68. data/test/gnupg_test_home/secring.gpg +0 -0
  69. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +22 -17
  70. data/test/integration/test_maildir.rb +75 -0
  71. data/test/integration/test_mbox.rb +69 -0
  72. data/test/test_crypto.rb +12 -2
  73. data/test/test_header_parsing.rb +1 -1
  74. data/test/test_helper.rb +6 -3
  75. data/test/test_message.rb +42 -342
  76. data/test/test_messages_dir.rb +4 -28
  77. data/test/test_yaml_regressions.rb +1 -1
  78. data/test/unit/test_contact.rb +33 -0
  79. data/test/unit/test_locale_fiddler.rb +15 -0
  80. data/test/unit/test_person.rb +37 -0
  81. metadata +108 -38
  82. data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
  83. data/test/gnupg_test_home/trustdb.gpg +0 -0
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'ruby-prof'
3
2
  require "redwood"
4
3
 
@@ -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.exists? fn
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.exists?(fn) && File.size(fn) > 0
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.exists? fn
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.exists? Redwood::SYNC_OK_FN
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.exists? Redwood::SYNC_OK_FN
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.exists? Redwood::SYNC_OK_FN
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.exists? filename
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
@@ -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!
@@ -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.exists? Redwood::COLOR_FN
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
 
@@ -16,7 +16,7 @@ class ContactManager
16
16
  @a2p = {} # alias to person
17
17
  @e2p = {} # email to person
18
18
 
19
- if File.exists? fn
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(old_aalias != nil and old_aalias != "") # remove old alias
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
@@ -10,11 +10,14 @@ class CryptoManager
10
10
 
11
11
  class Error < StandardError; end
12
12
 
13
- OUTGOING_MESSAGE_OPERATIONS = OrderedHash.new(
14
- [:sign, "Sign"],
15
- [:sign_and_encrypt, "Sign and encrypt"],
16
- [:encrypt, "Encrypt only"]
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
@@ -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.exists? dir
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 = 0
65
- while File.exists? fn_for_offset(i)
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.exists? fn_for_offset(offset)
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 fn_for_offset(offset) do |f|
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
@@ -83,7 +83,7 @@ class HookManager
83
83
  @contexts = {}
84
84
  @tags = {}
85
85
 
86
- Dir.mkdir dir unless File.exists? dir
86
+ Dir.mkdir dir unless File.exist? dir
87
87
  end
88
88
 
89
89
  attr_reader :tags
@@ -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.exists? @dir
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.exists? path
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?
@@ -15,7 +15,7 @@ class LabelManager
15
15
  def initialize fn
16
16
  @fn = fn
17
17
  labels =
18
- if File.exists? fn
18
+ if File.exist? fn
19
19
  IO.readlines(fn).map { |x| x.chomp.intern }
20
20
  else
21
21
  []
@@ -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
- uri = URI(@expanded_uri)
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.exists? tmp_path
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) { Dir.glob("#{subdir}/*").map { |x| File.join(d,File.basename(x)) }.sort }
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.exists? File.join(@dir, id)
204
+ File.exist? File.join(@dir, id)
194
205
  end
195
206
 
196
207
  private