actionmailer 0.3.0

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

Potentially problematic release.


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

Files changed (34) hide show
  1. data/CHANGELOG +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +102 -0
  4. data/install.rb +61 -0
  5. data/lib/action_mailer.rb +44 -0
  6. data/lib/action_mailer/base.rb +111 -0
  7. data/lib/action_mailer/mail_helper.rb +17 -0
  8. data/lib/action_mailer/vendor/text/format.rb +1447 -0
  9. data/lib/action_mailer/vendor/tmail.rb +4 -0
  10. data/lib/action_mailer/vendor/tmail/address.rb +223 -0
  11. data/lib/action_mailer/vendor/tmail/base64.rb +52 -0
  12. data/lib/action_mailer/vendor/tmail/config.rb +50 -0
  13. data/lib/action_mailer/vendor/tmail/encode.rb +447 -0
  14. data/lib/action_mailer/vendor/tmail/facade.rb +531 -0
  15. data/lib/action_mailer/vendor/tmail/header.rb +893 -0
  16. data/lib/action_mailer/vendor/tmail/info.rb +16 -0
  17. data/lib/action_mailer/vendor/tmail/loader.rb +1 -0
  18. data/lib/action_mailer/vendor/tmail/mail.rb +420 -0
  19. data/lib/action_mailer/vendor/tmail/mailbox.rb +414 -0
  20. data/lib/action_mailer/vendor/tmail/mbox.rb +1 -0
  21. data/lib/action_mailer/vendor/tmail/net.rb +261 -0
  22. data/lib/action_mailer/vendor/tmail/obsolete.rb +116 -0
  23. data/lib/action_mailer/vendor/tmail/parser.rb +1503 -0
  24. data/lib/action_mailer/vendor/tmail/port.rb +358 -0
  25. data/lib/action_mailer/vendor/tmail/scanner.rb +22 -0
  26. data/lib/action_mailer/vendor/tmail/scanner_r.rb +244 -0
  27. data/lib/action_mailer/vendor/tmail/stringio.rb +260 -0
  28. data/lib/action_mailer/vendor/tmail/tmail.rb +1 -0
  29. data/lib/action_mailer/vendor/tmail/utils.rb +215 -0
  30. data/rakefile +99 -0
  31. data/test/fixtures/templates/signed_up.rhtml +3 -0
  32. data/test/fixtures/test_mailer/signed_up.rhtml +3 -0
  33. data/test/mail_service_test.rb +53 -0
  34. metadata +86 -0
@@ -0,0 +1,16 @@
1
+ #
2
+ # info.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ module TMail
12
+
13
+ Version = '0.10.7'
14
+ Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
15
+
16
+ end
@@ -0,0 +1 @@
1
+ require 'tmail/mailbox'
@@ -0,0 +1,420 @@
1
+ #
2
+ # mail.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ require 'tmail/facade'
12
+ require 'tmail/encode'
13
+ require 'tmail/header'
14
+ require 'tmail/port'
15
+ require 'tmail/config'
16
+ require 'tmail/utils'
17
+ require 'socket'
18
+
19
+
20
+ module TMail
21
+
22
+ class Mail
23
+
24
+ class << self
25
+ def load( fname )
26
+ new(FilePort.new(fname))
27
+ end
28
+
29
+ alias load_from load
30
+ alias loadfrom load
31
+
32
+ def parse( str )
33
+ new(StringPort.new(str))
34
+ end
35
+ end
36
+
37
+ def initialize( port = nil, conf = DEFAULT_CONFIG )
38
+ @port = port || StringPort.new
39
+ @config = Config.to_config(conf)
40
+
41
+ @header = {}
42
+ @body_port = nil
43
+ @body_parsed = false
44
+ @epilogue = ''
45
+ @parts = []
46
+
47
+ @port.ropen {|f|
48
+ parse_header f
49
+ parse_body f unless @port.reproducible?
50
+ }
51
+ end
52
+
53
+ attr_reader :port
54
+
55
+ def inspect
56
+ "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
57
+ end
58
+
59
+ #
60
+ # to_s interfaces
61
+ #
62
+
63
+ public
64
+
65
+ include StrategyInterface
66
+
67
+ def write_back( eol = "\n", charset = 'e' )
68
+ parse_body
69
+ @port.wopen {|stream| encoded eol, charset, stream }
70
+ end
71
+
72
+ def accept( strategy )
73
+ with_multipart_encoding(strategy) {
74
+ ordered_each do |name, field|
75
+ next if field.empty?
76
+ strategy.header_name canonical(name)
77
+ field.accept strategy
78
+ strategy.puts
79
+ end
80
+ strategy.puts
81
+ body_port().ropen {|r|
82
+ strategy.write r.read
83
+ }
84
+ }
85
+ end
86
+
87
+ private
88
+
89
+ def canonical( name )
90
+ name.split(/-/).map {|s| s.capitalize }.join('-')
91
+ end
92
+
93
+ def with_multipart_encoding( strategy )
94
+ if parts().empty? # DO NOT USE @parts
95
+ yield
96
+
97
+ else
98
+ bound = ::TMail.new_boundary
99
+ if @header.key? 'content-type'
100
+ @header['content-type'].params['boundary'] = bound
101
+ else
102
+ store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
103
+ end
104
+
105
+ yield
106
+
107
+ parts().each do |tm|
108
+ strategy.puts
109
+ strategy.puts '--' + bound
110
+ tm.accept strategy
111
+ end
112
+ strategy.puts
113
+ strategy.puts '--' + bound + '--'
114
+ strategy.write epilogue()
115
+ end
116
+ end
117
+
118
+ ###
119
+ ### header
120
+ ###
121
+
122
+ public
123
+
124
+ ALLOW_MULTIPLE = {
125
+ 'received' => true,
126
+ 'resent-date' => true,
127
+ 'resent-from' => true,
128
+ 'resent-sender' => true,
129
+ 'resent-to' => true,
130
+ 'resent-cc' => true,
131
+ 'resent-bcc' => true,
132
+ 'resent-message-id' => true,
133
+ 'comments' => true,
134
+ 'keywords' => true
135
+ }
136
+ USE_ARRAY = ALLOW_MULTIPLE
137
+
138
+ def header
139
+ @header.dup
140
+ end
141
+
142
+ def []( key )
143
+ @header[key.downcase]
144
+ end
145
+
146
+ alias fetch []
147
+
148
+ def []=( key, val )
149
+ dkey = key.downcase
150
+
151
+ if val.nil?
152
+ @header.delete dkey
153
+ return nil
154
+ end
155
+
156
+ case val
157
+ when String
158
+ header = new_hf(key, val)
159
+ when HeaderField
160
+ ;
161
+ when Array
162
+ ALLOW_MULTIPLE.include? dkey or
163
+ raise ArgumentError, "#{key}: Header must not be multiple"
164
+ @header[dkey] = val
165
+ return val
166
+ else
167
+ header = new_hf(key, val.to_s)
168
+ end
169
+ if ALLOW_MULTIPLE.include? dkey
170
+ (@header[dkey] ||= []).push header
171
+ else
172
+ @header[dkey] = header
173
+ end
174
+
175
+ val
176
+ end
177
+
178
+ alias store []=
179
+
180
+ def each_header
181
+ @header.each do |key, val|
182
+ [val].flatten.each {|v| yield key, v }
183
+ end
184
+ end
185
+
186
+ alias each_pair each_header
187
+
188
+ def each_header_name( &block )
189
+ @header.each_key(&block)
190
+ end
191
+
192
+ alias each_key each_header_name
193
+
194
+ def each_field( &block )
195
+ @header.values.flatten.each(&block)
196
+ end
197
+
198
+ alias each_value each_field
199
+
200
+ FIELD_ORDER = %w(
201
+ return-path received
202
+ resent-date resent-from resent-sender resent-to
203
+ resent-cc resent-bcc resent-message-id
204
+ date from sender reply-to to cc bcc
205
+ message-id in-reply-to references
206
+ subject comments keywords
207
+ mime-version content-type content-transfer-encoding
208
+ content-disposition content-description
209
+ )
210
+
211
+ def ordered_each
212
+ list = @header.keys
213
+ FIELD_ORDER.each do |name|
214
+ if list.delete(name)
215
+ [@header[name]].flatten.each {|v| yield name, v }
216
+ end
217
+ end
218
+ list.each do |name|
219
+ [@header[name]].flatten.each {|v| yield name, v }
220
+ end
221
+ end
222
+
223
+ def clear
224
+ @header.clear
225
+ end
226
+
227
+ def delete( key )
228
+ @header.delete key.downcase
229
+ end
230
+
231
+ def delete_if
232
+ @header.delete_if do |key,val|
233
+ if Array === val
234
+ val.delete_if {|v| yield key, v }
235
+ val.empty?
236
+ else
237
+ yield key, val
238
+ end
239
+ end
240
+ end
241
+
242
+ def keys
243
+ @header.keys
244
+ end
245
+
246
+ def key?( key )
247
+ @header.key? key.downcase
248
+ end
249
+
250
+ def values_at( *args )
251
+ args.map {|k| @header[k.downcase] }.flatten
252
+ end
253
+
254
+ alias indexes values_at
255
+ alias indices values_at
256
+
257
+ private
258
+
259
+ def parse_header( f )
260
+ name = field = nil
261
+ unixfrom = nil
262
+
263
+ while line = f.gets
264
+ case line
265
+ when /\A[ \t]/ # continue from prev line
266
+ raise SyntaxError, 'mail is began by space' unless field
267
+ field << ' ' << line.strip
268
+
269
+ when /\A([^\: \t]+):\s*/ # new header line
270
+ add_hf name, field if field
271
+ name = $1
272
+ field = $' #.strip
273
+
274
+ when /\A\-*\s*\z/ # end of header
275
+ add_hf name, field if field
276
+ name = field = nil
277
+ break
278
+
279
+ when /\AFrom (\S+)/
280
+ unixfrom = $1
281
+
282
+ else
283
+ raise SyntaxError, "wrong mail header: '#{line.inspect}'"
284
+ end
285
+ end
286
+ add_hf name, field if name
287
+
288
+ if unixfrom
289
+ add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
290
+ end
291
+ end
292
+
293
+ def add_hf( name, field )
294
+ key = name.downcase
295
+ field = new_hf(name, field)
296
+
297
+ if ALLOW_MULTIPLE.include? key
298
+ (@header[key] ||= []).push field
299
+ else
300
+ @header[key] = field
301
+ end
302
+ end
303
+
304
+ def new_hf( name, field )
305
+ HeaderField.new(name, field, @config)
306
+ end
307
+
308
+ ###
309
+ ### body
310
+ ###
311
+
312
+ public
313
+
314
+ def body_port
315
+ parse_body
316
+ @body_port
317
+ end
318
+
319
+ def each( &block )
320
+ body_port().ropen {|f| f.each(&block) }
321
+ end
322
+
323
+ def body
324
+ parse_body
325
+ @body_port.ropen {|f|
326
+ return f.read
327
+ }
328
+ end
329
+
330
+ def body=( str )
331
+ parse_body
332
+ @body_port.wopen {|f| f.write str }
333
+ str
334
+ end
335
+
336
+ alias preamble body
337
+ alias preamble= body=
338
+
339
+ def epilogue
340
+ parse_body
341
+ @epilogue.dup
342
+ end
343
+
344
+ def epilogue=( str )
345
+ parse_body
346
+ @epilogue = str
347
+ str
348
+ end
349
+
350
+ def parts
351
+ parse_body
352
+ @parts
353
+ end
354
+
355
+ def each_part( &block )
356
+ parts().each(&block)
357
+ end
358
+
359
+ private
360
+
361
+ def parse_body( f = nil )
362
+ return if @body_parsed
363
+ if f
364
+ parse_body_0 f
365
+ else
366
+ @port.ropen {|f|
367
+ skip_header f
368
+ parse_body_0 f
369
+ }
370
+ end
371
+ @body_parsed = true
372
+ end
373
+
374
+ def skip_header( f )
375
+ while line = f.gets
376
+ return if /\A[\r\n]*\z/ === line
377
+ end
378
+ end
379
+
380
+ def parse_body_0( f )
381
+ if multipart?
382
+ read_multipart f
383
+ else
384
+ @body_port = @config.new_body_port(self)
385
+ @body_port.wopen {|w|
386
+ w.write f.read
387
+ }
388
+ end
389
+ end
390
+
391
+ def read_multipart( src )
392
+ bound = @header['content-type'].params['boundary']
393
+ is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
394
+ lastbound = "--#{bound}--"
395
+
396
+ ports = [ @config.new_preamble_port(self) ]
397
+ begin
398
+ f = ports.last.wopen
399
+ while line = src.gets
400
+ if is_sep === line
401
+ f.close
402
+ break if line.strip == lastbound
403
+ ports.push @config.new_part_port(self)
404
+ f = ports.last.wopen
405
+ else
406
+ f << line
407
+ end
408
+ end
409
+ @epilogue = (src.read || '')
410
+ ensure
411
+ f.close if f and not f.closed?
412
+ end
413
+
414
+ @body_port = ports.shift
415
+ @parts = ports.map {|p| self.class.new(p, @config) }
416
+ end
417
+
418
+ end # class Mail
419
+
420
+ end # module TMail
@@ -0,0 +1,414 @@
1
+ #
2
+ # mailbox.rb
3
+ #
4
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU Lesser General Public License version 2 or later.
9
+ #
10
+
11
+ require 'tmail/port'
12
+ require 'socket'
13
+ require 'mutex_m'
14
+
15
+
16
+ unless [].respond_to?(:sort_by)
17
+ module Enumerable#:nodoc:
18
+ def sort_by
19
+ map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ module TMail
26
+
27
+ class MhMailbox
28
+
29
+ PORT_CLASS = MhPort
30
+
31
+ def initialize( dir )
32
+ edir = File.expand_path(dir)
33
+ raise ArgumentError, "not directory: #{dir}"\
34
+ unless FileTest.directory? edir
35
+ @dirname = edir
36
+ @last_file = nil
37
+ @last_atime = nil
38
+ end
39
+
40
+ def directory
41
+ @dirname
42
+ end
43
+
44
+ alias dirname directory
45
+
46
+ attr_accessor :last_atime
47
+
48
+ def inspect
49
+ "#<#{self.class} #{@dirname}>"
50
+ end
51
+
52
+ def close
53
+ end
54
+
55
+ def new_port
56
+ PORT_CLASS.new(next_file_name())
57
+ end
58
+
59
+ def each_port
60
+ mail_files().each do |path|
61
+ yield PORT_CLASS.new(path)
62
+ end
63
+ @last_atime = Time.now
64
+ end
65
+
66
+ alias each each_port
67
+
68
+ def reverse_each_port
69
+ mail_files().reverse_each do |path|
70
+ yield PORT_CLASS.new(path)
71
+ end
72
+ @last_atime = Time.now
73
+ end
74
+
75
+ alias reverse_each reverse_each_port
76
+
77
+ # old #each_mail returns Port
78
+ #def each_mail
79
+ # each_port do |port|
80
+ # yield Mail.new(port)
81
+ # end
82
+ #end
83
+
84
+ def each_new_port( mtime = nil, &block )
85
+ mtime ||= @last_atime
86
+ return each_port(&block) unless mtime
87
+ return unless File.mtime(@dirname) >= mtime
88
+
89
+ mail_files().each do |path|
90
+ yield PORT_CLASS.new(path) if File.mtime(path) > mtime
91
+ end
92
+ @last_atime = Time.now
93
+ end
94
+
95
+ private
96
+
97
+ def mail_files
98
+ Dir.entries(@dirname)\
99
+ .select {|s| /\A\d+\z/ === s }\
100
+ .map {|s| s.to_i }\
101
+ .sort\
102
+ .map {|i| "#{@dirname}/#{i}" }\
103
+ .select {|path| FileTest.file? path }
104
+ end
105
+
106
+ def next_file_name
107
+ unless n = @last_file
108
+ n = 0
109
+ Dir.entries(@dirname)\
110
+ .select {|s| /\A\d+\z/ === s }\
111
+ .map {|s| s.to_i }.sort\
112
+ .each do |i|
113
+ next unless FileTest.file? "#{@dirname}/#{i}"
114
+ n = i
115
+ end
116
+ end
117
+ begin
118
+ n += 1
119
+ end while FileTest.exist? "#{@dirname}/#{n}"
120
+ @last_file = n
121
+
122
+ "#{@dirname}/#{n}"
123
+ end
124
+
125
+ end # MhMailbox
126
+
127
+ MhLoader = MhMailbox
128
+
129
+
130
+ class UNIXMbox
131
+
132
+ def UNIXMbox.lock( fname )
133
+ begin
134
+ f = File.open(fname)
135
+ f.flock File::LOCK_EX
136
+ yield f
137
+ ensure
138
+ f.flock File::LOCK_UN
139
+ f.close if f and not f.closed?
140
+ end
141
+ end
142
+
143
+ class << self
144
+ alias newobj new
145
+ end
146
+
147
+ def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
148
+ tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
149
+ newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
150
+ end
151
+
152
+ def UNIXMbox.static_new( fname, dir, readonly = false )
153
+ newobj(fname, dir, readonly, true)
154
+ end
155
+
156
+ def initialize( fname, mhdir, readonly, static )
157
+ @filename = fname
158
+ @readonly = readonly
159
+ @closed = false
160
+
161
+ Dir.mkdir mhdir
162
+ @real = MhMailbox.new(mhdir)
163
+ @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
164
+ ObjectSpace.define_finalizer self, @finalizer
165
+ end
166
+
167
+ def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
168
+ lambda {
169
+ if writeback_p
170
+ lock(mboxfile) {|f|
171
+ mh.each_port do |port|
172
+ f.puts create_from_line(port)
173
+ port.ropen {|r|
174
+ f.puts r.read
175
+ }
176
+ end
177
+ }
178
+ end
179
+ if cleanup_p
180
+ Dir.foreach(mh.dirname) do |fname|
181
+ next if /\A\.\.?\z/ === fname
182
+ File.unlink "#{mh.dirname}/#{fname}"
183
+ end
184
+ Dir.rmdir mh.dirname
185
+ end
186
+ }
187
+ end
188
+
189
+ # make _From line
190
+ def UNIXMbox.create_from_line( port )
191
+ sprintf 'From %s %s',
192
+ fromaddr(), TextUtils.time2str(File.mtime(port.filename))
193
+ end
194
+
195
+ def UNIXMbox.fromaddr
196
+ h = HeaderField.new_from_port(port, 'Return-Path') ||
197
+ HeaderField.new_from_port(port, 'From') or return 'nobody'
198
+ a = h.addrs[0] or return 'nobody'
199
+ a.spec
200
+ end
201
+ private_class_method :fromaddr
202
+
203
+ def close
204
+ return if @closed
205
+
206
+ ObjectSpace.undefine_finalizer self
207
+ @finalizer.call
208
+ @finalizer = nil
209
+ @real = nil
210
+ @closed = true
211
+ @updated = nil
212
+ end
213
+
214
+ def each_port( &block )
215
+ close_check
216
+ update
217
+ @real.each_port(&block)
218
+ end
219
+
220
+ alias each each_port
221
+
222
+ def reverse_each_port( &block )
223
+ close_check
224
+ update
225
+ @real.reverse_each_port(&block)
226
+ end
227
+
228
+ alias reverse_each reverse_each_port
229
+
230
+ # old #each_mail returns Port
231
+ #def each_mail( &block )
232
+ # each_port do |port|
233
+ # yield Mail.new(port)
234
+ # end
235
+ #end
236
+
237
+ def each_new_port( mtime = nil )
238
+ close_check
239
+ update
240
+ @real.each_new_port(mtime) {|p| yield p }
241
+ end
242
+
243
+ def new_port
244
+ close_check
245
+ @real.new_port
246
+ end
247
+
248
+ private
249
+
250
+ def close_check
251
+ @closed and raise ArgumentError, 'accessing already closed mbox'
252
+ end
253
+
254
+ def update
255
+ return if FileTest.zero?(@filename)
256
+ return if @updated and File.mtime(@filename) < @updated
257
+ w = nil
258
+ port = nil
259
+ time = nil
260
+ UNIXMbox.lock(@filename) {|f|
261
+ begin
262
+ f.each do |line|
263
+ if /\AFrom / === line
264
+ w.close if w
265
+ File.utime time, time, port.filename if time
266
+
267
+ port = @real.new_port
268
+ w = port.wopen
269
+ time = fromline2time(line)
270
+ else
271
+ w.print line if w
272
+ end
273
+ end
274
+ ensure
275
+ if w and not w.closed?
276
+ w.close
277
+ File.utime time, time, port.filename if time
278
+ end
279
+ end
280
+ f.truncate(0) unless @readonly
281
+ @updated = Time.now
282
+ }
283
+ end
284
+
285
+ def fromline2time( line )
286
+ m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
287
+ or return nil
288
+ Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
289
+ end
290
+
291
+ end # UNIXMbox
292
+
293
+ MboxLoader = UNIXMbox
294
+
295
+
296
+ class Maildir
297
+
298
+ extend Mutex_m
299
+
300
+ PORT_CLASS = MaildirPort
301
+
302
+ @seq = 0
303
+ def Maildir.unique_number
304
+ synchronize {
305
+ @seq += 1
306
+ return @seq
307
+ }
308
+ end
309
+
310
+ def initialize( dir = nil )
311
+ @dirname = dir || ENV['MAILDIR']
312
+ raise ArgumentError, "not directory: #{@dirname}"\
313
+ unless FileTest.directory? @dirname
314
+ @new = "#{@dirname}/new"
315
+ @tmp = "#{@dirname}/tmp"
316
+ @cur = "#{@dirname}/cur"
317
+ end
318
+
319
+ def directory
320
+ @dirname
321
+ end
322
+
323
+ def inspect
324
+ "#<#{self.class} #{@dirname}>"
325
+ end
326
+
327
+ def close
328
+ end
329
+
330
+ def each_port
331
+ mail_files(@cur).each do |path|
332
+ yield PORT_CLASS.new(path)
333
+ end
334
+ end
335
+
336
+ alias each each_port
337
+
338
+ def reverse_each_port
339
+ mail_files(@cur).reverse_each do |path|
340
+ yield PORT_CLASS.new(path)
341
+ end
342
+ end
343
+
344
+ alias reverse_each reverse_each_port
345
+
346
+ def new_port
347
+ fname = nil
348
+ tmpfname = nil
349
+ newfname = nil
350
+
351
+ begin
352
+ fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
353
+
354
+ tmpfname = "#{@tmp}/#{fname}"
355
+ newfname = "#{@new}/#{fname}"
356
+ end while FileTest.exist? tmpfname
357
+
358
+ if block_given?
359
+ File.open(tmpfname, 'w') {|f| yield f }
360
+ File.rename tmpfname, newfname
361
+ PORT_CLASS.new(newfname)
362
+ else
363
+ File.open(tmpfname, 'w') {|f| f.write "\n\n" }
364
+ PORT_CLASS.new(tmpfname)
365
+ end
366
+ end
367
+
368
+ def each_new_port
369
+ mail_files(@new).each do |path|
370
+ dest = @cur + '/' + File.basename(path)
371
+ File.rename path, dest
372
+ yield PORT_CLASS.new(dest)
373
+ end
374
+
375
+ check_tmp
376
+ end
377
+
378
+ TOO_OLD = 60 * 60 * 36 # 36 hour
379
+
380
+ def check_tmp
381
+ old = Time.now.to_i - TOO_OLD
382
+
383
+ each_filename(@tmp) do |full, fname|
384
+ if FileTest.file? full and
385
+ File.stat(full).mtime.to_i < old
386
+ File.unlink full
387
+ end
388
+ end
389
+ end
390
+
391
+ private
392
+
393
+ def mail_files( dir )
394
+ Dir.entries(dir)\
395
+ .select {|s| s[0] != ?. }\
396
+ .sort_by {|s| s.slice(/\A\d+/).to_i }\
397
+ .map {|s| "#{dir}/#{s}" }\
398
+ .select {|path| FileTest.file? path }
399
+ end
400
+
401
+ def each_filename( dir )
402
+ Dir.foreach(dir) do |fname|
403
+ path = "#{dir}/#{fname}"
404
+ if fname[0] != ?. and FileTest.file? path
405
+ yield path, fname
406
+ end
407
+ end
408
+ end
409
+
410
+ end # Maildir
411
+
412
+ MaildirLoader = Maildir
413
+
414
+ end # module TMail