3 = Mailbox and Mbox interaction class
7 # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
9 # Permission is hereby granted, free of charge, to any person obtaining
10 # a copy of this software and associated documentation files (the
11 # "Software"), to deal in the Software without restriction, including
12 # without limitation the rights to use, copy, modify, merge, publish,
13 # distribute, sublicense, and/or sell copies of the Software, and to
14 # permit persons to whom the Software is furnished to do so, subject to
15 # the following conditions:
17 # The above copyright notice and this permission notice shall be
18 # included in all copies or substantial portions of the Software.
20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
29 # with permission of Minero Aoki.
37 unless [].respond_to
?(:sort_by)
38 module Enumerable
#:nodoc:
40 map
{|i
| [yield(i
), i
] }.sort
{|a
,b
| a
.first
<=> b
.first
}.map
{|i
| i
[1] }
53 edir
= File
.expand_path(dir
)
54 raise ArgumentError
, "not directory: #{dir}"\
55 unless FileTest
.directory
? edir
65 alias dirname directory
67 attr_accessor
:last_atime
70 "#<#{self.class} #{@dirname}>"
77 PORT_CLASS
.new(next_file_name())
81 mail_files().each
do |path
|
82 yield PORT_CLASS
.new(path
)
84 @last_atime = Time
.now
90 mail_files().reverse_each
do |path
|
91 yield PORT_CLASS
.new(path
)
93 @last_atime = Time
.now
96 alias reverse_each reverse_each_port
98 # old #each_mail returns Port
100 # each_port do |port|
101 # yield Mail.new(port)
105 def each_new_port( mtime
= nil, &block
)
106 mtime
||= @last_atime
107 return each_port(&block
) unless mtime
108 return unless File
.mtime(@dirname) >= mtime
110 mail_files().each
do |path
|
111 yield PORT_CLASS
.new(path
) if File
.mtime(path
) > mtime
113 @last_atime = Time
.now
119 Dir
.entries(@dirname)\
120 .select
{|s
| /\A\d+\z/ === s
}\
123 .map
{|i
| "#{@dirname}/#{i}" }\
124 .select
{|path
| FileTest
.file
? path
}
128 unless n
= @last_file
130 Dir
.entries(@dirname)\
131 .select
{|s
| /\A\d+\z/ === s
}\
132 .map
{|s
| s
.to_i
}.sort\
134 next unless FileTest
.file
? "#{@dirname}/#{i}"
140 end while FileTest
.exist
? "#{@dirname}/#{n}"
157 # Creates a new mailbox object that you can iterate through to collect the
158 # emails from with "each_port".
160 # You need to pass it a filename of a unix mailbox format file, the format of this
161 # file can be researched at this page at {wikipedia}[link:http://en.wikipedia.org/wiki/Mbox]
165 # +filename+: The filename of the mailbox you want to open
167 # +tmpdir+: Can be set to override TMail using the system environment's temp dir. TMail will first
168 # use the temp dir specified by you (if any) or then the temp dir specified in the Environment's TEMP
169 # value then the value in the Environment's TMP value or failing all of the above, '/tmp'
171 # +readonly+: If set to false, each email you take from the mail box will be removed from the mailbox.
172 # default is *false* - ie, it *WILL* truncate your mailbox file to ZERO once it has read the emails out.
180 # # First show using readonly true:
183 # File.size("../test/fixtures/mailbox")
186 # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox", nil, true)
187 # #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=true.....>
189 # mailbox.each_port do |port|
190 # mail = TMail::Mail.new(port)
198 # File.size?("../test/fixtures/mailbox")
201 # # Now show with readonly set to the default false
203 # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox")
204 # #=> #<TMail::UNIXMbox:0x14a2aa8 @readonly=false.....>
206 # mailbox.each_port do |port|
207 # mail = TMail::Mail.new(port)
215 # File.size?("../test/fixtures/mailbox")
217 def UNIXMbox
.new( filename
, tmpdir
= nil, readonly
= false )
218 tmpdir
= ENV['TEMP'] || ENV['TMP'] || '/tmp'
219 newobj(filename
, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly
, false)
222 def UNIXMbox
.lock( fname
)
224 f
= File
.open(fname
, 'r+')
225 f
.flock File
::LOCK_EX
228 f
.flock File
::LOCK_UN
229 f
.close
if f
and not f
.closed
?
233 def UNIXMbox
.static_new( fname
, dir
, readonly
= false )
234 newobj(fname
, dir
, readonly
, true)
237 def initialize( fname
, mhdir
, readonly
, static
)
243 @real = MhMailbox
.new(mhdir
)
244 @finalizer = UNIXMbox
.mkfinal(@real, @filename, !@readonly, !static
)
245 ObjectSpace
.define_finalizer
self, @finalizer
248 def UNIXMbox
.mkfinal( mh
, mboxfile
, writeback_p
, cleanup_p
)
252 mh
.each_port
do |port
|
253 f
.puts
create_from_line(port
)
261 Dir
.foreach(mh
.dirname
) do |fname
|
262 next if /\A\.\.?\z/ === fname
263 File
.unlink
"#{mh.dirname}/#{fname}"
271 def UNIXMbox
.create_from_line( port
)
272 sprintf
'From %s %s',
273 fromaddr(), TextUtils
.time2str(File
.mtime(port
.filename
))
276 def UNIXMbox
.fromaddr(port
)
277 h
= HeaderField
.new_from_port(port
, 'Return-Path') ||
278 HeaderField
.new_from_port(port
, 'From') ||
279 HeaderField
.new_from_port(port
, 'EnvelopeSender') or return 'nobody'
280 a
= h
.addrs
[0] or return 'nobody'
287 ObjectSpace
.undefine_finalizer
self
295 def each_port( &block
)
298 @real.each_port(&block
)
303 def reverse_each_port( &block
)
306 @real.reverse_each_port(&block
)
309 alias reverse_each reverse_each_port
311 # old #each_mail returns Port
312 #def each_mail( &block )
313 # each_port do |port|
314 # yield Mail.new(port)
318 def each_new_port( mtime
= nil )
321 @real.each_new_port(mtime
) {|p
| yield p
}
332 @closed and raise ArgumentError
, 'accessing already closed mbox'
336 return if FileTest
.zero
?(@filename)
337 return if @updated and File
.mtime(@filename) < @updated
341 UNIXMbox
.lock(@filename) {|f
|
344 if /\AFrom / === line
346 File
.utime time
, time
, port
.filename
if time
348 port
= @real.new_port
350 time
= fromline2time(line
)
356 if w
and not w
.closed
?
358 File
.utime time
, time
, port
.filename
if time
361 f
.truncate(0) unless @readonly
366 def fromline2time( line
)
367 m
= /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line
) \
369 Time
.local(m
[6].to_i
, m
[1], m
[2].to_i
, m
[3].to_i
, m
[4].to_i
, m
[5].to_i
)
374 MboxLoader
= UNIXMbox
381 PORT_CLASS
= MaildirPort
384 def Maildir
.unique_number
391 def initialize( dir
= nil )
392 @dirname = dir
|| ENV['MAILDIR']
393 raise ArgumentError
, "not directory: #{@dirname}"\
394 unless FileTest
.directory
? @dirname
395 @new = "#{@dirname}/new"
396 @tmp = "#{@dirname}/tmp"
397 @cur = "#{@dirname}/cur"
405 "#<#{self.class} #{@dirname}>"
412 mail_files(@cur).each
do |path
|
413 yield PORT_CLASS
.new(path
)
419 def reverse_each_port
420 mail_files(@cur).reverse_each
do |path
|
421 yield PORT_CLASS
.new(path
)
425 alias reverse_each reverse_each_port
433 fname
= "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
435 tmpfname
= "#{@tmp}/#{fname}"
436 newfname
= "#{@new}/#{fname}"
437 end while FileTest
.exist
? tmpfname
440 File
.open(tmpfname
, 'w') {|f
| yield f
}
441 File
.rename tmpfname
, newfname
442 PORT_CLASS
.new(newfname
)
444 File
.open(tmpfname
, 'w') {|f
| f
.write
"\n\n" }
445 PORT_CLASS
.new(tmpfname
)
450 mail_files(@new).each
do |path
|
451 dest
= @cur + '/' + File
.basename(path
)
452 File
.rename path
, dest
453 yield PORT_CLASS
.new(dest
)
459 TOO_OLD
= 60 * 60 * 36 # 36 hour
462 old
= Time
.now
.to_i
- TOO_OLD
464 each_filename(@tmp) do |full
, fname
|
465 if FileTest
.file
? full
and
466 File
.stat(full
).mtime
.to_i
< old
474 def mail_files( dir
)
476 .select
{|s
| s
[0] != ?. }\
477 .sort_by
{|s
| s
.slice(/\A\d+/).to_i
}\
478 .map
{|s
| "#{dir}/#{s}" }\
479 .select
{|path
| FileTest
.file
? path
}
482 def each_filename( dir
)
483 Dir
.foreach(dir
) do |fname
|
484 path
= "#{dir}/#{fname}"
485 if fname
[0] != ?. and FileTest
.file
? path
493 MaildirLoader
= Maildir